local util = {} util.PARENTNODE = ".." -- 一个常量,表示上级节点的路径。 util.CURRENTNODE = "." -- 一个常量,表示同级节点的路径。 util.FUNC_HASVALUE_TRUE = function() return true end util.FUNC_HASVALUE_FALSE = function() return false end util.FUNC_VALUE_NIL = function() return nil end --定义一个常量,表示该节点值不存在。 util.NOVALUE = { hasValue = util.FUNC_HASVALUE_FALSE, value = util.FUNC_VALUE_NIL } util.FUNC__NOTSUPPORTED = function() util.error("不支持本函数。", util.ELID_ERROR) end util.KEYCOMPARER_NGROUP = function(k1, k2, params) local key1, ngpage1 = util.getNGroup(k1) local key2, ngpage2 = util.getNGroup(k2) local loadNGData = function(path) local title = mw.title.new(path) if title.exists then local success, data = pcall(mw.loadData, path) if success then return { path = path, data = data } else util.error(mw.ustring.format("加载页面“%s”时发生错误:%s", path, data), util.ELID_WARNING) return { path = path, data = {} } -- 设为空Lua表。 end else util.error(mw.ustring.format("不存在页面“%s”", ngpage), util.ELID_WARNING) return { path = ngpage, data = {} } end end if key1 == nil and key2 == nil then -- 两个键均不为名称组。 return util.normalize(k1) == util.normalize(k2) else local customizedngpage = nil -- 自定义的名称组页面路径。 local customizedngdata = nil -- 自定义的名称组数据。 if params ~= nil then -- 从params参数中获取自定义的名称组页面路径。 if type(params.ngpage) == "string" then customizedngpage = params.ngpage elseif type(params.ngpage) == "table" then customizedngpage = params.ngpage.path customizedngdata = params.ngpage.data end end -- 第二个键为名称组,且传入了params参数,该行为应当视为异常。 if key2 ~= nil and params ~= nil then util.error(mw.ustring.format("试图在查找一个名称组“%s”。", k2), util.ELID_ERROR) end if key1 ~= nil and key2 ~= nil then -- 两个键均为名称组。 return key1 == key2 and (ngpage1 or customizedngpage) == (ngpage2 or customizedngpage) elseif key1 ~= nil then -- 第一个键为名称组,第二个键不为名称组。 if ngpage1 ~= nil and ngpage1 ~= customizedngpage then -- 节点中设置了名称组所属页面路径,且与params参数中的页面路径不同。 customizedngpage = ngpage1 -- 加载节点中设置的名称组所属页面路径。 customizedngdata = loadNGData(customizedngpage).data elseif type(params.ngpage) == "string" then -- 数据未加载。 -- 加载节点中设置的名称组所属页面路径。 customizedngdata = loadNGData(customizedngpage).data -- 将加载的数据保存入params参数中,以后不必二次耗时加载。 if type(params.ngpage) == "table" then params.ngpage.data = customizedngdata else params.ngpage = { path = customizedngpage, data = customizedngdata } end end if customizedngdata == nil or customizedngdata[key1] == nil then -- 无法获取名称组列表,或列表中没有key1这个名称组的键。 return false else -- 在列表中搜索。 for _, ngKey in ipairs(customizedngdata[key1]) do if util.normalize(ngKey) == util.normalize(k2) then return true end -- 组内只要有一个名称与第二个名称相等。 end return false end elseif key2 ~= nil then -- 第一个键不为名称组,第二个键为名称组。 return false end end end util.EL_FATAL = "fatal" -- 一个常量,表示错误级别【致命错误】。 util.ELID_FATAL = 1 -- 一个常量,表示错误级别ID【致命错误】。 util.EL_ERROR = "error" -- 一个常量,表示错误级别【错误】。 util.ELID_ERROR = 2 -- 一个常量,表示错误级别ID【错误】。 util.EL_WARNING = "warning" -- 一个常量,表示错误级别【警告】。 util.ELID_WARNING = 3 -- 一个常量,表示错误级别ID【警告】 util.errorlevel = "error" -- 全局的错误级别,可人为修改。 local getErrorLevelID = function(errorlevel) if type(errorlevel) == "string" then if errorlevel == util.EL_WARNING then return util.ELID_WARNING elseif errorlevel == util.EL_ERROR then return util.ELID_ERROR elseif errorlevel == util.EL_FATAL then return util.ELID_FATAL else local elid = tonumber(errorlevel) if elid == nil then return util.ELID_ERROR else return elid end end elseif type(errorlevel) == "number" then return errorlevel else return util.ELID_ERROR end end --[[ 根据指定的错误级别抛出一个错误。 ----- message - 错误信息 errorlevel - 错误级别 --]] function util.error(message, errorlevel) if getErrorLevelID(errorlevel) <= getErrorLevelID(util.errorlevel) then error(message) end end --[[ 标准化名称格式。 1. 下划线字符视为空白字符。 2. 多个空白字符视为一个下划线字符。 3. 首字母为$字符表示名称组。 4. 名称组的方括号内为其所在的页面地址。 ----- name - 要被标准化的名称 ----- 返回:标准化的名称 ----- 错误 - 名称中出现无效字符。:名称里出现了中括号、正反斜杠等不被允许的字符。 错误 - 名称中的页面路径中出现无效字符。:名称中的页面路径里出现了中括号、正反斜杠等不被允许的字符。 --]] function util.normalize(name) if name == nil then return nil elseif type(name) ~= "string" then return util.error(mw.ustring.format("在normalize中name的值类型错误。(应为%s,实际为%s)", "string", type(name)), util.ELID_FATAL) end local normalizeKey = function(key) -- 首字符$表示名称组,若需要在键中使用$字符则需使用“%$”。 -- 获取转义后的键。 key = mw.ustring.gsub(key, "%%[%%$]", function(m) -- 替换掉 if m == "%%" then return "%" elseif m == "%$" then return "$" else return "" end end) key = mw.ustring.gsub(key, "[_%s]+", "_") -- 替换多个空白字符为一个空白字符。 local v1, v2 = mw.ustring.find(key, "[%[%]/\\]") -- 检测无效字符。 if v1 ~= nil then util.error(mw.ustring.format("名称“%s”中出现无效字符“%s”", key, mw.ustring.sub(key, v1, v2)), util.ELID_FATAL) end return key end if mw.ustring.sub(name, 1, 1) == "$" then -- 若第一个字符是$号,则表示是名称组。 name = mw.ustring.sub(name, 2) local page, ngroup = mw.ustring.match(name, "^%[([^%[%]]*)%]([^%[%]/\\]+)$") if page ~= nil then -- 含有页面地址的名称组格式正确。 if page ~= "" then local title = mw.title.new(page) if title == nil then util.error(mw.ustring.format("名称中的页面路径“%s”中出现无效字符。", page), util.ELID_FATAL) else page = title.prefixedText -- 获取自动转换的标准页面路径。 end end ngroup = normalizeKey(ngroup) -- 标准化键名称。 return mw.ustring.format("$[%s]%s", page, ngroup) end ngroup = mw.ustring.match(name, "^[^%[%]/\\]+$") if ngroup ~= nil then -- 名称组格式正确。 ngroup = normalizeKey(ngroup) -- 标准化键名称。 return mw.ustring.format("$%s", ngroup) end util.error(mw.ustring.format("名称组“%s”格式不正确", mw.ustring.sub(name, 1, 1)), util.ELID_FATAL) else name = normalizeKey(name) if name == "" then util.error(mw.ustring.format("名称“%s”为空。", name), util.ELID_WARNING) end return name end return nil end --[[ 返回名称组的键及定义页面路径。 ----- name - 要检查的名称。 ----- 返回:ngroup, page ngroup - 名称组的键。 page - 定义名组的页面路径。未定义时返回nil。 --]] function util.getNGroup(name) name = util.normalize(name) -- 获取标准化名称。 if name == nil then return nil end -- 获取失败。 local page, ngroup = mw.ustring.match(name, "%$%[([^%]]*)%](.+)$") if page == nil then ngroup = mw.ustring.match(name, "%$(.+)$") end if page == "" then page = nil end -- 空页面视为nil。 return ngroup, page end --[[ 检查名称是否为名称组。 ----- name - 要检查的名称。 ----- 返回:名称是否为名称组。 --]] function util.IsNGroup(name) return util.getNGroup(name) == nil end --[[ 合并一系列路径。函数将会调用util.normalize标准化路径片段,并整理逻辑层级。 ----- seg1 - (至少有一个)路径片段 [匿名参数列表] - 其余的路径片段 ----- 返回:合并后的路径 --]] function util.pathCombine(seg1, ...) local path = {} if seg1 ~= nil then table.insert(path, seg1) end for i = 1, select("#", ...) do local seg = select(i, ...) if seg ~= nil then table.insert(path, seg) end end local result = util.pathSplit(path) return table.concat(result, " ") end --[[ 将路径切割成为多个片段。每个片段会被util.normalize标准化。 ----- path - 要被切割的路径。 ----- 返回:路径片段数组。 --]] function util.pathSplit(path) if path == nil then return {} else local sort = {} local splitPathString = function(pathStr) local pos = 1 while pos <= mw.ustring.len(pathStr) + 1 do -- 对字符串路径,按斜杠、反斜杠及空格进行拆分,剔除空名称。 local v1, v2 = mw.ustring.find(pathStr, "^%[[^%[%]]*%][^/\\%s]*", pos) local v3, v4 = mw.ustring.find(pathStr, "^[^/\\%s]*", pos) if v1 == nil and v3 == nil then break elseif v1 ~= nil and (v3 == nil or v1 <= v3) then table.insert(sort, util.normalize(mw.ustring.sub(pathStr, v1, v2))) pos = v2 + 2 elseif v3 ~= nil and (v1 == nil or v1 > v3) then table.insert(sort, util.normalize(mw.ustring.sub(pathStr, v3, v4))) pos = v4 + 2 else -- 硬编码错误:必然是BUG的例外情况,理论上应当永远不执行。 error("意料之外的错误。") end end end -- 对路径中的每个元素进行格式整理。 if type(path) == "string" then splitPathString(path) elseif type(path) == "table" then -- 对Lua表表示的路径,递归拆解Lua表,剔除空名称和非字符串的值。 local function unpackInnerTable(_tab) for _, item in ipairs(_tab) do if type(item) == "string" then splitPathString(item) elseif type(item) == "table" then unpackInnerTable(item) end end end unpackInnerTable(path) end local result = {} -- 对整理结果进行逻辑整理。 -- 去除“..”(返回上级目录)、“.”(同级目录)等控制目录名称。 for index, item in ipairs(sort) do if item == util.CURRENTNODE then -- 同级目录) -- 不添加也不删除目录层次。 elseif item == util.PARENTNODE then -- 上级目录 if #result == 0 then util.error(mw.ustring.format("不能获取\"%s\"的上级目录,因为已经是根目录。", table.concat(sort, "/", 1, index - 1)), util.ELID_ERROR) else table.remove(result, #result) -- 删除最后一个层次。 end else -- 下级目录 table.insert(result, item) -- 在最后添加一个层次。 end end return result end end return util