2023年政策修订增补工作正在进行中,欢迎参与!
  • Moegirl.ICU:萌娘百科流亡社群 581077156(QQ),欢迎对萌娘百科运营感到失望的编辑者加入
  • Moegirl.ICU:账号认领正在试运行,有意者请参照账号认领流程

Module:少女歌剧

萌娘百科,万物皆可萌的百科全书!转载请标注来源页面的网页链接,并声明引自萌娘百科。内容不可商用。
跳转到导航 跳转到搜索
Template-info.svg 模块文档  [创建] [刷新]
local module = {}

module.BASEPAGE = "Module:少女歌剧" -- 常量,表示获取少女歌剧专题信息查询模块根页面路径。
module.DATABASEPAGE = module.BASEPAGE .. "/Data" -- 常量,表示获取Data的模块根页面路径。
module.NGROUPPAGE = module.BASEPAGE .. "/NGroup" -- 常量,表示获取名称组列表的模块根页面路径。

local mps = require("Module:ModulePageSystem")

--[==[
加载某一个模块页中的数据。
--]==]
local loadData = function(page, ignoreerror)
    if page == nil then return nil end

    local title = mw.title.new(page)
    if title == nil then
        mps.util.error(mw.ustring.format("加载数据页\"%s\"时发生错误:%s", page, "页面路径地址不符合规范。"), mps.util.ELID_WARNING)
        return nil
    elseif not title.exists then
        mps.util.error(mw.ustring.format("加载数据页\"%s\"时发生错误:%s", page, "页面不存在。"), mps.util.ELID_WARNING)
        return nil
    end

    local success, result = pcall(mw.loadData, page)
    if success then
        return result
    else
        mps.util.error(mw.ustring.format("加载数据页\"%s\"时发生错误:%s", page, result), mps.util.ELID_WARNING)
        return nil
    end
end

--[==[
获取传入的参数,若#invoke时传入至少一个以数字为参数名的参数,则使用#invoke时传入的参数列表;否则使用包装了模块#invoke的模板传入的参数列表。
--]==]
local getArgs = function(frame)
    local args
    if (function()
        for k, _ in pairs(frame.args) do
            if type(k) == "number" then
                return true
            end
        end
    end)() then
        args = frame.args
    else
        args = require("Module:Arguments").getArgs(frame:getParent() or frame)
    end

    return args
end

--[==[
获取分级索引关键词。
--]==]
local getPath = function(args, normalize)
    local path = {}
    for _, name in ipairs(args) do
        if normalize == true then name = mps.util.normalize(name) end
        table.insert(path, name)
    end

    return path
end

-- 可匹配多个正则。
mw.ustring.gsub_m = function(s, patterns, repl, n)
    local length = mw.ustring.len(s)
    local init = 1
    local v1, v2
    local i = 0
    local ss = {}
    while (n == nil or i < n) and init < length do
        v1, v2 = length + 1, length
        local p = nil
        for _, pattern in ipairs(patterns) do
            local v3, v4 = mw.ustring.find(s, pattern, init, false)
            if v3 ~= nil and (v3 < v1 or (v3 == v1 and v4 <= v2)) then
                v1, v2 = v3, v4 -- 更新匹配范围。
                p = pattern -- 更新使用的正则。
            end
        end
        if p ~= nil then
            table.insert(ss, mw.ustring.sub(s, init, v1 - 1))
            local new_s = mw.ustring.gsub(mw.ustring.sub(s, v1, v2), p, repl, 1) -- 调用原生替换函数。
            table.insert(ss, new_s)
        else break
        end
        init = v2 + 1
        i = i + 1
    end
    table.insert(ss, mw.ustring.sub(s, init))
    
    return table.concat(ss)
end
module.gsub_m = function(str, func)
    local patterns = {
        "(%$(%$))",
        "(%$%(([^)]*)%))",
        "(%$(%d))",
        "(%$([^($]))"
    }
    return mw.ustring.gsub_m(str, patterns, func or "-")
end
function module.data(frame)
    local args = getArgs(frame)

    local ignoreerror = frame.args.ignoreerror == "yes" or args.ignoreerror == "yes" or false

    local path = getPath(args)

    local page = frame.args.page or args.page or module.DATABASEPAGE
    local params = { ngpage = { path = module.NGROUPPAGE, data = loadData(module.NGROUPPAGE) or {} } }
    local result = loadData(page, true) or {}
    for index, name in ipairs(path) do
        if type(result) ~= "table" then -- 上级节点含有值。
            -- 回溯一次查询,并将上级节点的键作为目录尝试加载本级页面,以重新从本级页面中查询。
            page = mw.ustring.format("%s/%s", page, path[index - 1])
            result = loadData(page, true) or {}
        end

        local keyexists = false
        for cname, subnodes in pairs(result) do
            if mps.util.KEYCOMPARER_NGROUP(cname, name, params) then -- 表中含有这个值
                result = subnodes
                keyexists = true
            end
        end

        if not keyexists then
            -- 尝试加载次级页面,以从次级页面中继续查询。
            page = mw.ustring.format("%s/%s", page, name)
            result = loadData(page, true) or {}
        end
    end

    if type(result) == "table" then -- 查询结果不是值。
        if not ignoreerror then error("查询到的值为空(可能在指定名称上的值的类型不是字符串,或者指定名称不存在)。")
        else return nil
        end
    else
        -- 成功查询到值。
        return result
    end
end

--[==[
【将要弃用】
--]==]
local vardefine = function(name, value, frame)
    local wiki
    if mw.ustring.sub(value, 1, 1) == "$" then -- 是wiki
        wiki = mw.ustring.gsub(mw.ustring.sub(value, 2), "%%[%%$]", function(m)
            if m == "%%" then return "%"
            elseif m == "%$" then return "$"
            else return ""
            end
        end)
        wiki = frame:preprocess(wiki)
    else
        wiki = value
    end

    return frame:callParserFunction("#vardefine", name, wiki)
end

--[==[
将指定的名称组通过{{#vardefine}}写入到维基文本中。
--]==]
function module.ngroup(frame)
    local ngroup = mw.loadData(module.NGROUPPAGE)
    
    local vdexprs = {}
    for gname, names in pairs(ngroup) do
        if type(gname) == "string" then
            for _, name in ipairs(names) do
                local expr = vardefine(mw.ustring.format("%s %s", "名称组", name), gname, frame)
                if expr ~= nil then
                    table.insert(vdexprs, expr) -- 添加定义语句。
                end
            end
        end
    end
    
    return table.concat(vdexprs)
end

--[==[
将指定的数据通过{{#vardefine}}写入到维基文本中。
--]==]
function module.define(frame)
    local args = getArgs(frame)
    local datapage
    if mw.ustring.match(args.datapage or "", "^[/\\]") then -- 是相对路径
        datapage = module.DATABASEPAGE .. args.datapage
    else -- 是绝对路径
        datapage = args.datapage
    end
    local ngroup = mw.loadData(module.NGROUPPAGE)

    local vdexprs = {}
    local function defineInternal(data, path, dir)
        if type(data) == "table" then
            if #path == 0 then table.insert(path, "$") end -- 若path提供的匹配列表没有查询到底层节点,则插入默认的匹配所有键正则以查询到底层节点。
        elseif type(data) == "string" then
            if #path > 0 then error("无法继续向下层节点查询,因为当前节点已经是底层节点。") end

            local expr = vardefine(dir, data, frame)
            if expr ~= nil then
                table.insert(vdexprs, expr) -- 添加定义语句。
            end
            return
        else return -- 不支持其他类型的值。
        end
        local pattern = path[1]
        local isngroup = mw.ustring.sub(pattern, 1, 2) == "$$" -- 仅匹配名称组时在开头添加两个$字符。
        local isall = not isngroup and (mw.ustring.sub(pattern, 1, 1) == "$") -- 匹配所有键时在开头添加一个$字符。
        for dkey, dvalue in pairs(data) do
            if isall or (mw.ustring.sub(dkey, 1, 1) == "$") == isngroup then -- 同或,表示数据键和查找键正则同时是或同时不是名称组。
                if mw.ustring.sub(dkey, 1, 1) == "$" then -- 数据键是名称组。
                    local ngkey = mps.util.normalize(mw.ustring.sub(dkey, 2)) -- 获取名称组键。
                    if isall then
                        pattern = mw.ustring.sub(pattern, 2) -- 获取正则。
                    elseif isngroup then
                        pattern = mw.ustring.sub(pattern, 3) -- 获取正则。
                    end
                    if mw.ustring.match(ngkey, pattern) then -- 匹配成功。
                        local newdata = dvalue
                        local newpath = {}
                        for i = 2, #path do table.insert(newpath, path[i]) end
                        local newdir
                        if dir == nil then
                            newdir = ngkey
                        else
                            newdir = mw.ustring.format("%s %s", dir, ngkey)
                        end
                        defineInternal(newdata, newpath, newdir)
                    end
                else -- 数据键不是名称组。
                    dkey = mps.util.normalize(dkey)
                    if isall then
                        pattern = mw.ustring.sub(pattern, 2) -- 获取正则。
                    end
                    if mw.ustring.match(dkey, pattern) then -- 匹配成功。
                        local newdata = dvalue
                        local newpath = {}
                        for i = 2, #path do table.insert(newpath, path[i]) end
                        local newdir
                        if dir == nil then
                            newdir = dkey
                        else
                            newdir = mw.ustring.format("%s %s", dir, dkey)
                        end
                        defineInternal(newdata, newpath, newdir)
                    end
                end
            end
        end
    end
    defineInternal(mw.loadData(datapage), getPath(args), nil)

    return table.concat(vdexprs)
end

-- Only for Template:九九组成员对她的称呼 and Template:舞台少女称呼表
function module.aishou(frame)
    local args = frame.args
    local data = mw.loadData(module.DATABASEPAGE.."/角色称呼表")
    
    local name = args.name or "爱城华恋" -- 她的名字。
    local node = mw.html.create("table")
        :css("font-size", "89%")
        :css("text-align", "center")
        :css("max-width", "260px")
        :css("float", "right")
        :css("background-color", "white")
        :tag("tr"):tag("th")
            :css("color", "white")
            :css("background-color", frame:callParserFunction{ name = "#var", args = { name } })
            :css("font-size", "100%")
            :css("font-weight", "bold")
            :css("padding", "1em")
            :attr("colspan", 2)
            :wikitext("其他人对")
            :wikitext(frame:expandTemplate{ title = "少女歌剧/角色信息", args = { name, "姓名地区转换" } })
            :wikitext("的称呼")
            :allDone()
    
    for _, cname in ipairs(module.sortNameGroup()) do
        local cothers = data["$" .. cname] or {}
        for cother, ass in pairs(cothers) do
            cother = mw.text.trim(cother, "$") -- 简单处理。
            if name == cother then
                
                -- 处理格式字符串 --
                local as_process = function(as)
                    local patterns = {
                        "(%$(%$))",
                        "(%$%(([^)]*)%))",
                        "(%$([^($]))"
                    }
                    -- 进行通用格式字符串替换,获得新格式字符串。
                    as = mw.ustring.gsub_m(as, patterns, function(rawtext, matchtext)
                        if rawtext == "$$" then return "$$"
                        else
                            local numindex = tonumber(matchtext) -- 如果索引值是数字,优先转换为number类型键。
                            if numindex then
                                return data.COMMON[numindex] or data.COMMON[matchtext] or rawtext
                            else
                                if not mw.ustring.find(matchtext, "^日文") then
                                    matchtext = "日文"..matchtext -- 在前方添加“日文”。
                                    rawtext = "$("..matchtext..")" -- 在前方添加“日文”。
                                end
                                return data.COMMON[matchtext] or rawtext
                            end
                        end
                    end)
                    as = mw.ustring.gsub_m(as, patterns, function(rawtext, matchtext)
                        if rawtext == "$$" then return "$"
                        else
                            return frame:expandTemplate{ title = "少女歌剧/角色信息", args = { name, matchtext } } -- 调用模板,获取角色信息。
                        end
                    end)
                    return as
                end
                
                local empty = false
                if type(ass) == "table" then
                    local _ass = {}
                    for i_as, as in ipairs(ass) do
                        _ass[i_as] = frame:expandTemplate{ title = "lj", args = { as_process(as) } }
                    end
                    if #_ass == 0 then empty = true
                    else
                        ass = table.concat(_ass, "、")
                    end
                else
                    if mw.text.trim(ass) == "" then empty = true
                    else
                        ass = frame:expandTemplate{ title = "lj", args = { as_process(ass) } }
                    end
                end
                
                if not empty then
                    local ltext
                    if name == cname then
                        ltext = "自称"
                    else
                        ltext = 
                        frame:expandTemplate{ title = "少女歌剧/角色表述", args =
                        {
                            cname,
                            "内链",
                            frame:expandTemplate{ title = "Color", args = 
                            {
                                "white",
                                frame:expandTemplate{ title = "少女歌剧/角色信息", args = { cname, "名地区转换" } }
                            } }
                        } }
                    end
                    --------------------
                    
                    node:tag("tr")
                        :tag("td")
                            :css("color", "white")
                            :css("background-color", frame:callParserFunction{ name = "#var", args = { cname } })
                            :css("font-weight", "bold")
                            :css("padding", "0 1em")
                            :css("min-width", "80px")
                            :wikitext(ltext)
                            :done()
                        :tag("td")
                            :css("padding", "0 1em")
                            :css("min-width", "140px")
                            :wikitext(ass)
                end
            end
        end
    end
    
    return tostring(node)
end

-- 对名称组列表list,按categories(匿名函数)列表指示的顺序排序。
function module.sortNameGroup(list, --[[ categories ]]...)
    local categories = {}
    -- 整理排序分类列表。
    for i = 1, select("#", ...) do
        local cat = select(i, ...)
        if cat then
            table.insert(categories, cat)
        end
    end
    
    local data = loadData(module.NGROUPPAGE) -- 加载名称组数据。
    if #categories == 0 then categories = data[0] end -- 使用默认排序分类列表。
    
    local result = {}
    for _, cat in ipairs(categories) do
        for _, catinfo in ipairs(data) do
            if catinfo.category == cat then
                for _, _ng in ipairs(catinfo) do
                    if type(list) == "table" then
                        for _, ng in ipairs(list) do
                            if ng == _ng then
                                table.insert(result, ng)
                            end
                        end
                    elseif type(list) == "string" and list == _ng then
                        table.insert(result, list)
                    elseif list == nil then
                        table.insert(result, _ng)
                    else
                        mps.util.error(mw.ustring.format("参数list的值类型错误。(应为%s,实际为%s)", "table、string或nil", type(list)), mps.util.ELID_ERROR)
                    end
                end
            end
        end
    end
    
    return result
end

return module