2023年政策修订增补工作正在进行中,欢迎参与!
Module:Sandbox/サンムル/ModulePageSystem/util
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