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