置顶公告:【置顶】关于临时开启评论区所有功能的公告(2022.10.22) | 【置顶】关于本站Widget恢复使用的公告
  • 你好~!欢迎来到萌娘百科镜像站!如需查看或编辑,请联系本站管理员注册账号。
  • 本镜像站和其他萌娘百科的镜像站无关,请注意分别。

Module:Sandbox/サンムル/ModulePageSystem

猛汉♂百科,万男皆可猛的百科全书!转载请标注来源页面的网页链接,并声明引自猛汉百科。内容不可商用。
跳到导航 跳到搜索
Template-info.svg 模块文档  [创建] [刷新]
local mps = {}
mps.util = require("Module:Sandbox/サンムル/ModulePageSystem/util")
local dictionary = require("Module:Dictionary")

--[[
    验证一个节点是否是格式正确的对象。
    通过检测数个字段、函数是否存在,类型是否正确来验证节点格式是否正确。
    -----
    node - 将要验证的节点。
    validations - 指定了验证的类型,若有多个验证类型,则以空白字符分隔,验证结果将取与。
        value - 该节点支持存储值。
        sub - 该节点支持包含次级节点。
    -----
    返回:多个验证类型结果的与值。
--]]
function mps.validate(node, validations)
    if node == nil then return false
    elseif type(node) ~= "table" then return false
    end
    if type(validations) == "table" then validations = table.concat(validations, " ")
    elseif type(validations) ~= "string" then return false
    end

    for validation in mw.ustring.gmatch(validations, "%S+") do
        if validation == "value" then
            if type(node.hasValue) ~= "function" or
                type(node.value) ~= "function" then return false end
        elseif validation == "ref" then
            if type(node.getTarget) ~= "function" then return false end
        elseif validation == "sub" then
            if type(node.plain) ~= "function" or
                type(node.wiki) ~= "function" or
                type(node.ref) ~= "function" or
                type(node.sub) ~= "function" or
                type(node.substart) ~= "function" or
                type(node.subend) ~= "function" or
                type(node.format) ~= "function" or
                type(node.find) ~= "function" then return false end
        else
            mps.util.error(mw.ustring.format("不支持的验证类型:\"%s\"", validation), mps.util.ELID_WARNING)
            return false
        end
    end

    return true
end

--[[
    从指定的节点开始按路径查找值。
    -----
    node - 指定的节点。
    path - 路径
    -----
    返回:路径name处的值。
--]]
function mps.find(node, path, params)
    if type(params) ~= "table" then params = {} end
    if type(params.path) ~= "table" then params.path = {} end
    if type(params.basestack) ~= "table" then params.basestack = {} end
    if type(params.refhistory) ~= "table" then params.refhistory = dictionary.create() end

    if node == nil or type(node) == "string" then -- node为表示以这个字符串为网页路径创建的节点。
        -- 若node为空,则设为调用页面的标题。
        node = node or mw.title.getCurrentTitle().prefixedText

        -- 尝试加载页面。
        local root = mps.load({}, node)
        if mps.validate(node, "value") and value:hasValue(params) then
            -- 以这个字符串为基地址创建根节点。
            node = mps.create(node)
        else
            node = root
        end
    end

    if params.root == nil then params.root = node end -- 将自身设为查询根节点。

    local value
    local subpath = mps.util.pathSplit(path)

    -- 截取顶层目录为当前名称;其余为下级名称。
    local name = subpath[1]
    table.remove(subpath, 1)
    table.insert(params.path, name) -- 将当前名称添加到路径中。
    table.insert(params.basestack, { node.base }) -- 将当前所属页面路径添加到页面路径堆栈中。(由于所属页面路径可能为nil,故用table包裹。)

    -- 筛选子节点。子节点是否为底层节点将对其是否会被接受有影响。
    -- 底层节点:接受值节点;不接受含有子节点的节点。
    -- 非底层节点:接受含有子节点的节点;不接受值节点。
    local findInternal = function(_value)
        -- 获取节点值。
        if mps.validate(_value, "ref") then -- 如果是引用节点
            -- 获取引用节点指向的节点。
            _value = _value:getTarget(params)
        end
        if (params.novalidation or false) == true then -- 若忽略子节点是否为底层节点对其是否会被接受的影响。
            if #subpath == 0 then -- 仅当子节点为底层节点时生效。
                if (mps.validate(_value, "value") and _value:hasValue(params)) or mps.validate(_value, "sub") then
                    -- 仅在子节点是含有值的值节点或者含有子节点的节点时,直接返回子节点本身。
                    return _value
                end
            end
        end

        if #subpath == 0 and mps.validate(_value, "value") then -- 如果是底层节点,且节点是值节点。
            if _value:hasValue(params) then  -- 如果有值则返回节点。
                return _value
            end
        elseif #subpath ~= 0 and mps.validate(_value, "sub") then -- 如果不是底层节点,且节点含有子节点。
            -- 进行递归查找。
            _value = _value:find(subpath, params)
            if mps.validate(_value, "value") and _value:hasValue(params) then -- 子节点查找到值则返回节点。
                return _value
            elseif mps.validate(_value, "sub") then -- 从findInternal返回的含有子节点的节点绝对是忽略层级影响。
                return _value
            end
        elseif #subpath == 0 and not (mps.validate(_value, "value") or mps.validate(_value, "sub")) then -- 节点不是任何节点,且是底层节点。
            mps.util.error("非节点对象。", mps.util.ELID_ERROR)
        end

        return mps.util.NOVALUE -- 查找不到子节点树的值。
    end

    if mps.validate(node, "sub") then -- 本节点含有子节点。
        -- 寻找键name。
        local hasKey, _value = node.dic:tryGetValue(name, params)
        if hasKey then
            value = findInternal(_value)
            if mps.validate(value, "value") and value:hasValue(params) then
                table.remove(params.path, #params.path) -- 从路径中删除最后一段名称。
                table.remove(params.basestack, #params.basestack) -- 从页面路径堆栈中删除最后一段所属页面路径。
                return value
            elseif mps.validate(value, "sub") then -- 从findInternal返回的含有子节点的节点绝对是忽略层级影响。
                table.remove(params.path, #params.path) -- 从路径中删除最后一段名称。
                table.remove(params.basestack, #params.basestack) -- 从页面路径堆栈中删除最后一段所属页面路径。
                return value
            end
        end

        -- 寻找通用函数formatfunc。
        if node.formatfunc ~= nil then -- 找到通用函数formatfunc。
            -- 获取处理结果。
            value = node:formatfunc(name, params)

            value = findInternal(value)
            if mps.validate(value, "value") and value:hasValue(params) then
                table.remove(params.path, #params.path) -- 从路径中删除最后一段名称。
                table.remove(params.basestack, #params.basestack) -- 从页面路径堆栈中删除最后一段所属页面路径。
                return value
            elseif mps.validate(value, "sub") then -- 从findInternal返回的含有子节点的节点绝对是忽略层级影响。
                table.remove(params.path, #params.path) -- 从路径中删除最后一段名称。
                table.remove(params.basestack, #params.basestack) -- 从页面路径堆栈中删除最后一段所属页面路径。
                return value
            end
        end
    end

    -- 尝试加载页面。
    local fullpagename = nil
    -- 沿堆栈向下查询上级节点的基路径。
    for i = #params.basestack, 1, -1 do
        if #params.basestack[i] ~= 0 then
            fullpagename = params.basestack[i][1]
            break;
        end
    end
    if fullpagename == nil then
        -- 无法获取基路径。
        mps.util.error(mw.ustring.format("无法获取节点“%s”基路径,因为上层节点均未定义基路径。", mps.util.pathCombine(params.path)), mps.util.ELID_WARNING)
    else
        fullpagename = mw.ustring.format("%s/%s", fullpagename, table.concat(mps.util.pathSplit(params.path), "/")) -- 要加载的页面。
        value = findInternal(mps.load(node, fullpagename))
        if mps.validate(value, "value") and value:hasValue(params) then -- 页面加载成功。
            table.remove(params.path, #params.path) -- 从路径中删除最后一段名称。
            table.remove(params.basestack, #params.basestack) -- 从页面路径堆栈中删除最后一段所属页面路径。
            return value
        elseif mps.validate(value, "sub") then -- 从findInternal返回的含有子节点的节点绝对是忽略层级影响。
            table.remove(params.path, #params.path) -- 从路径中删除最后一段名称。
            table.remove(params.basestack, #params.basestack) -- 从页面路径堆栈中删除最后一段所属页面路径。
            return value
        end
    end

    table.remove(params.path, #params.path) -- 从路径中删除最后一段名称。
    table.remove(params.basestack, #params.basestack) -- 从页面路径堆栈中删除最后一段所属页面路径。
    return mps.util.NOVALUE -- 查询不到值。
end

mps.FUNC_SUBEND_ERROR = function(self)
    mps.util.error("语法错误:subend。(当前并未处于定义子项的上下文。)", mps.util.ELID_WARNING)
    return self
end

--[[
    从一个页面地址创建一个节点。
    -----
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。
    -----
    返回:新节点。
--]]
function mps.create(page)
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("在create中参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL)
    elseif page == nil then -- 检测页面地址是否为空。
        mps.util.error(mw.ustring.format("页面地址为空。", page), mps.util.ELID_WARNING)
    elseif type(page) == "string" and mw.title.new(page) == nil then -- 检测页面地址是否包含不合法字符。
        mps.util.error(mw.ustring.format("页面地址“%s”包含不合法字符。", page), mps.util.ELID_ERROR)
    end

    local prototype = {
        dic = dictionary.create(mps.util.KEYCOMPARER_NGROUP),
        base = page, -- 本节点所在的页面地址。
        inheritbase = "", -- [未启用]以定义了页面地址的最近一个上层节点开始到本节点的路径,即本节点的“继承”得到的基路径。
        plain = mps.registerPlain,
        wiki = mps.registerWiki,
        ref = mps.registerRef,
        sub = mps.registerSubpage,
        substart = mps.registerNewSubpage,
        subend = mps.FUNC_SUBEND_ERROR,
        format = mps.registerFormat,
        find = mps.find
    }
    return prototype
end

mps.FUNC_VALUE_PLAIN = function(self) return self.innervalue end

--[[
    创建一个纯文本的值节点。
    -----
    plain - 文本内容。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。
    -----
    返回:新值节点。
--]]
function mps.createPlain(plain, page)
    if plain ~= nil and type(plain) ~= "string" then mps.util.error(mw.ustring.format("参数plain的值类型错误。(应为%s,实际为%s)", "nil或string", type(plain)), mps.util.ELID_FATAL) end
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL)
    elseif page == nil then -- 检测页面地址是否为空。
        mps.util.error(mw.ustring.format("页面地址为空。", page), mps.util.ELID_WARNING)
    elseif type(page) == "string" and mw.title.new(page) == nil then -- 检测页面地址是否包含不合法字符。
        mps.util.error(mw.ustring.format("页面地址“%s”包含不合法字符。", page), mps.util.ELID_ERROR)
    end

    return {
        base = page,
        innervalue = plain,
        hasValue = mps.util.FUNC_HASVALUE_TRUE,
        value = mps.FUNC_VALUE_PLAIN
    }
end

mps.FUNC_VALUE_WIKI = function(self, params)
    if self.innervalue == nil then return nil
    elseif params.plain then return self.innervalue
    elseif self.innerwiki ~= nil then return self.innerwiki
    elseif params == nil or params.frame == nil then
        mps.util.error(mw.ustring.format("无法获取wiki执行对象(frame的值为nil)。"), mps.util.ELID_WARNING)
    else
        self.innerwiki = params.frame:preprocess(self.innervalue)
        return self.innerwiki
    end
end

--[[
    创建一个Wiki内容的值节点。
    -----
    wiki - Wiki内容。
    plain - 指示wiki参数的字符串值是否是纯文本,默认为true。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。
    -----
    返回:新值节点。
--]]
function mps.createWiki(wiki, plain, page)
    if wiki ~= nil and type(wiki) ~= "string" then mps.util.error(mw.ustring.format("参数wiki的值类型错误。(应为%s,实际为%s)", "nil或string", type(wiki)), mps.util.ELID_FATAL) end
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL)
    elseif page == nil then -- 检测页面地址是否为空。
        mps.util.error(mw.ustring.format("页面地址为空。", page), mps.util.ELID_WARNING)
    elseif type(page) == "string" and mw.title.new(page) == nil then -- 检测页面地址是否包含不合法字符。
        mps.util.error(mw.ustring.format("页面地址“%s”包含不合法字符。", page), mps.util.ELID_ERROR)
    end

    local result = {
        base = page,
        innervalue = wiki,
        innerwiki = nil,
        hasValue = mps.util.FUNC_HASVALUE_TRUE,
        value = mps.FUNC_VALUE_WIKI
    }

    if (plain or false) ~= true then result.innerwiki = wiki end -- 默认是以纯文本形式创建。
    return result
end

local find_ref = function(node, params)
    -- 查找引用历史,检测是否循环引用。
    local flag = false
    local path = mps.util.pathCombine(params.path)
    if params.refhistory:hasKey(path) then
        -- 检测到循环引用。
        flag = true
    else
        for _, rh_entry in params.refhistory:enum() do
            if rh_entry.value == node then
                -- 检测到循环引用。
                flag = true
                break
            end
        end
    end

    if flag then
        local errorinfo = {}
        for _, _entry in params.refhistory:enum() do
            table.insert(errorinfo, _entry.key)
        end
        table.insert(errorinfo, rh_entry.key)

        mps.util.error("形成了循环引用。(“%s”)", table.concat(errorinfo, "”→“"), mps.util.ELID_ERROR)
        return mps.util.NOVALUE
    end

    -- 构建引用目标节点的路径。
    local newPath
    if node.isabsolute then -- 绝对路径
        newPath = mps.util.pathCombine(node.innervalue)
    else -- 相对路径
        newPath = mps.util.pathCombine(params.path, mps.util.PARENTNODE, node.innervalue)
    end
    -- 构建查找引用目标节点的新参数。
    local newParams = {
        frame = params.frame,
        ngpage = params.ngpage,
        plain = params.plain,
        refhistory = dictionary.create(nil, params.refhistory):add(path, node), -- 新建引用历史字典的副本,并将自身添加进去。
        novalidation = true -- 忽略节点所处层级的影响。
    }

    return mps.find(params.root, newPath, newParams), newParams
end

mps.FUNC_GETTARGET_REF = function(self, params)
    local target, newParams = find_ref(self, params)

    -- 递归获得指向的目标节点。
    if mps.validate(target, "ref") then
        return target:getTarget(target, newParams)
    else
        return target
    end
end

--[[
    创建一个引用节点。
    -----
    target - 引用指向的目标。
    isabsolute - 指示target参数表示的引用目标路径是否为绝对路径,默认为true。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。
    -----
    返回:新引用节点。
--]]
function mps.createRef(target, isabsolute, page)
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL)
    elseif page == nil then -- 检测页面地址是否为空。
        mps.util.error(mw.ustring.format("页面地址为空。", page), mps.util.ELID_WARNING)
    elseif type(page) == "string" and mw.title.new(page) == nil then -- 检测页面地址是否包含不合法字符。
        mps.util.error(mw.ustring.format("页面地址“%s”包含不合法字符。", page), mps.util.ELID_ERROR)
    end

    return {
        base = page,
        innervalue = target,
        isabsolute = (isabsolute or false) == true,
        getTarget = mps.FUNC_GETTARGET_REF
    }
end

--[[
    向节点中注册一个纯文本的子值节点。
    -----
    parent - 节点本身。
    name - 子节点名称。
    plain - 文本内容。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。若为非空值,则将覆盖参数parent的继承。
    -----
    返回:节点本身。(为语法糖提供支持)
--]]
function mps.registerPlain(parent, name, plain, page)
    if type(parent) == "nil" then mps.util.error(mw.ustring.format("参数parent的值类型错误。(应为%s,实际为%s)", "table", type(parent)), mps.util.ELID_FATAL) end
    if type(name) ~= "string" then mps.util.error(mw.ustring.format("参数name的值类型错误。(应为%s,实际为%s)", "string", type(name)), mps.util.ELID_FATAL) end
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL) end

    plain = mps.createPlain(plain, page or parent.page)
    if parent.dic:hasKey(name) then
        if parent.dic:getValue(name) ~= nil and parent.dic:getValue(name):hasValue() then
            mps.util.error(mw.ustring.format("在对节点\"%s\"赋值时失败,该节点已存在另外的值。", name), mps.util.ELID_ERROR)
        else
            parent.dic:setValue(name, plain)
        end
    else
        parent.dic:add(mps.util.normalize(name), plain)
    end

    return parent
end

--[[
    向节点中注册一个Wiki内容的子值节点。
    -----
    parent - 节点本身。
    name - 子节点名称。
    wiki - Wiki内容。
    plain - 指示wiki参数的字符串值是否是纯文本,默认为true。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。若为非空值,则将覆盖参数parent的继承。
    -----
    返回:节点本身。(为语法糖提供支持)
--]]
function mps.registerWiki(parent, name, wiki, plain, page)
    if type(parent) == "nil" then mps.util.error(mw.ustring.format("参数parent的值类型错误。(应为%s,实际为%s)", "table", type(parent)), mps.util.ELID_FATAL) end
    if type(name) ~= "string" then mps.util.error(mw.ustring.format("参数name的值类型错误。(应为%s,实际为%s)", "string", type(name)), mps.util.ELID_FATAL) end
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL) end

    wiki = mps.createWiki(wiki, plain, page or parent.page)
    if parent.dic:hasKey(name) then
        if parent.dic:getValue(name) ~= nil and parent.dic:getValue(name):hasValue() then
            mps.util.error(mw.ustring.format("在对节点\"%s\"赋值时失败,该节点已存在另外的值。", name), mps.util.ELID_ERROR)
        else
            parent.dic:setValue(name, wiki)
        end
    else
        parent.dic:add(mps.util.normalize(name), wiki)
    end

    return parent
end

--[[
    向节点中注册一个引用的子值节点。
    -----
    parent - 节点本身。
    names - 所有引用子节点的名称(被指向目标的别名)。若类型为string则表示一个名称;若类型为table则表示多个名称。
    target - 引用指向的目标。
    isabsolute - 指示target参数表示的引用目标路径是否为绝对路径,默认为true。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。若为非空值,则将覆盖参数parent的继承。
    -----
    返回:节点本身。(为语法糖提供支持)
--]]
function mps.registerRef(parent, names, target, isabsolute, page)
    if type(parent) == "nil" then mps.util.error(mw.ustring.format("参数parent的值类型错误。(应为%s,实际为%s)", "table", type(parent)), mps.util.ELID_FATAL) end
    if type(names) == "string" then names = { names }
    elseif type(names) ~= "table" then mps.util.error(mw.ustring.format("参数names的值类型错误。(应为%s,实际为%s)", "string或table", type(names)), mps.util.ELID_FATAL)
    end
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL) end

    local ref
    for i, name in ipairs(names) do
        if type(name) ~= "string" then
            mps.util.error(mw.ustring.format("参数names的第%d项的值类型错误。(应为%s,实际为%s)", i, "string", type(name)), mps.util.ELID_FATAL)
        else
            ref = ref or mps.createRef(target, isabsolute, page or parent.base) -- 只创建一次,整个循环使用同一且唯一的引用节点。

            if parent.dic:hasKey(name) then
                if parent.dic:getValue(name) ~= nil and parent.dic:getValue(name):hasValue() then
                    mps.util.error(mw.ustring.format("在对节点\"%s\"赋值时失败,该节点已存在另外的值。", name), mps.util.ELID_ERROR)
                else
                    parent.dic:setValue(name, ref)
                end
            else
                parent.dic:add(mps.util.normalize(name), ref)
            end
        end
    end

    return parent
end

--[[
    向节点中注册一个现有的子节点。
    这个子节点可以是从另一个页面导入、在本地定义的——node节点、储存了纯数据的普通Lua表。
    -----
    parent - 节点本身。
    name - 子节点名称。
    node - 将作为被添加的子节点的内容。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。若为非空值,则将覆盖参数parent的继承。
    -----
    返回:节点本身。(为语法糖提供支持)
--]]
function mps.registerSubpage(parent, name, node, page)
    if type(parent) == "nil" then mps.util.error(mw.ustring.format("参数parent的值类型错误。(应为%s,实际为%s)", "table", type(parent)), mps.util.ELID_FATAL) end
    if type(name) ~= "string" then mps.util.error(mw.ustring.format("参数name的值类型错误。(应为%s,实际为%s)", "string", type(name)), mps.util.ELID_FATAL) end
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL) end

    if type(node) == "string" then -- 是从指定路径的页面导入的内容。
        -- node默认是绝对路径,若需要引用节点所处页面路径则需使用“%0”。
        -- 获取绝对路径。
        local path = mw.ustring.gsub(node, "%%[%%0]", function(m)
            if m == "%%" then return "%"
            elseif m == "%0" then return parent.base
            else return ""
            end
        end)
        -- 加载页面。
        node = mps.load(parent, path)
    elseif type(node) == "table" then -- 是在本地定义的内容。
        if mps.validate(node, "sub") then -- 检测是否是含有子节点的节点对象。
            node = mps.derive(parent, node, page) -- 创建一个node的继承节点。
        elseif mps.validate(node, "value") then -- 检测是否是值节点。
            -- 设置所属页面路径。
            node.base = node.base or page or parent.base -- 取值原则:本地定义的值节点的已定义所属页面路径 → 覆盖参数parent继承的页面路径 → 参数parent继承的页面路径。
        else
            node = mps.parse(parent, node, page) -- 从一个普通Lua表转换成为节点。
        end
    end

    if parent.dic:hasKey(name) then
        if parent.dic:getValue(name) ~= nil and parent.dic:getValue(name):hasValue() then
            mps.util.error(mw.ustring.format("在对节点\"%s\"赋值时失败,该节点已存在另外的值。", name), mps.util.ELID_ERROR)
        else
            parent.dic:setValue(name, node)
        end
    else
        parent.dic:add(mps.util.normalize(name), node)
    end

    return parent
end

mps.FUNC_SUBEND_SUB = function(self) return self.parent end

--[[
    向节点中注册一个子节点,并切换到子节点的上下文。
    这个子节点可以是从另一个模板导入、在本地定义的——node节点、储存了纯数据的普通Lua表。
    -----
    parent - 节点本身。
    name - 子节点名称。
    node - 将作为被添加的子节点的内容。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。若为非空值,则将覆盖参数parent的继承。
    -----
    返回:子节点本身。(为语法糖提供支持)
--]]
function mps.registerNewSubpage(parent, name)
    if type(parent) == "nil" then mps.util.error(mw.ustring.format("参数parent的值类型错误。(应为%s,实际为%s)", "table", type(parent)), mps.util.ELID_FATAL) end
    if type(name) ~= "string" then mps.util.error(mw.ustring.format("参数name的值类型错误。(应为%s,实际为%s)", "string", type(name)), mps.util.ELID_FATAL) end

    local prototype = mps.create(parent.page) -- 获取一个标准的节点对象。
    prototype.inheritbase = mps.util.pathCombine(parent.inheritbase, name) -- 继承本节点的路径组成新路径。
    prototype.parent = parent -- 将本节点保存在parent字段中
    prototype.subend = mps.FUNC_SUBEND_SUB -- 结束子节点定义,返回到其父节点,即本节点。

    if parent.dic:hasKey(name) then
        if parent.dic:getValue(name) ~= nil and parent.dic:getValue(name):hasValue() then
            mps.util.error(mw.ustring.format("在对节点\"%s\"赋值时失败,该节点已存在另外的值。", name), mps.util.ELID_ERROR)
        else
            parent.dic:setValue(name, prototype)
        end
    else
        parent.dic:add(mps.util.normalize(name), prototype)
    end

    return prototype
end

function mps.registerFormat(parent, format)
    if type(parent) == "nil" then mps.util.error(mw.ustring.format("参数parent的值类型错误。(应为%s,实际为%s)", "table", type(parent)), mps.util.ELID_FATAL) end
    if format == nil then return
    elseif type(format) ~= "function" then mps.util.error(mw.ustring.format("通用函数formatfunc类型错误。(应为%s,实际为%s)", "function", type(format)), mps.util.ELID_FATAL)
    end

    if parent.formatfunc == nil then
        parent.formatfunc = format
    else
        mps.util.error("通用函数赋值失败,该节点已存在另外的通用函数。", mps.util.ELID_ERROR)
    end

    return parent
end

--[[
    从指定页面加载节点。
    -----
    parent - 本节点。
    path - 包含节点的模块页面地址。
    -----
    返回:指定页面包含的节点。
--]]
function mps.load(parent, path)
    if type(parent) == "nil" then mps.util.error(mw.ustring.format("在load中参数parent的值类型错误。(应为%s,实际为%s)", "table", type(parent)), mps.util.ELID_FATAL) end
    if type(path) ~= "string" then mps.util.error(mw.ustring.format("在load中参数path的值类型错误。(应为%s,实际为%s)", "string", type(path)), mps.util.ELID_FATAL) end

    local success, result = pcall(function(_path)
        local title = mw.title.new(_path) -- 获取要加载的页面的标题对象。
        if title == nil then
            error(mw.ustring.format("页面“%s”路径格式不正确。"), _path)
        elseif title.exists then -- 加载的页面存在。
            return require(_path)
        else
            mps.util.error(mw.ustring.format("页面“%s”不存在。", _path), mps.util.ELID_WARNING)
            return mps.util.NOVALUE
        end
    end, path)
    if result == mps.util.NOVALUE then return result
    elseif not success then
        mps.util.error(result, mps.util.ELID_ERROR)
        return mps.util.NOVALUE
    end

    local node = result

    if mps.validate(node, "sub") then -- 检测是否是节点对象。
        node = mps.derive(parent, node, path)
    else
        node = mps.parse(parent, node, path)
    end

    return node
end

--[[
    将一个节点设为本节点的继承节点。
    -----
    parent - 本节点。
    sub - 要被设为继承节点的节点。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。若为非空值,则将覆盖参数parent的继承。
    -----
    返回:被设为继承节点的节点。
--]]
function mps.derive(parent, sub, page)
    if type(parent) == "nil" then mps.util.error(mw.ustring.format("在derive中参数parent的值类型错误。(应为%s,实际为%s)", "table", type(parent)), mps.util.ELID_FATAL) end
    if type(sub) ~= "table" then mps.util.error(mw.ustring.format("在derive中参数sub的值类型错误。(应为%s,实际为%s)", "table", type(sub)), mps.util.ELID_FATAL) end
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL) end

    sub.base = sub.base or page or parent.base

    return sub
end

--[[
    将一个普通Lua表转换成为本节点的子节点。
    -----
    parent - 本节点。
    tab - 要被转换为本节点的子节点的普通Lua表。
    page - 定义这个节点的页面,应当是你在书写这个代码时所处的模块名页面全名。若为非空值,则将覆盖参数parent的继承。
    -----
    返回:转换后的节点。
--]]
function mps.parse(parent, tab, page)
    if type(parent) == "nil" then mps.util.error(mw.ustring.format("在parse中参数parent的值类型错误。(应为%s,实际为%s)", "table", type(parent)), mps.util.ELID_FATAL) end
    if type(tab) ~= "table" then mps.util.error(mw.ustring.format("在parse中参数tab的值类型错误。(应为%s,实际为%s)", "table", type(tab)), mps.util.ELID_FATAL) end
    if page ~= nil and type(page) ~= "string" then mps.util.error(mw.ustring.format("参数page的值类型错误。(应为%s,实际为%s)", "nil或string", type(page)), mps.util.ELID_FATAL) end

    local node = mps.create(page or parent.base)
    for _name, _node in pairs(tab) do
        if type(_node) == "table" then
            -- 递归转换Lua表中的节点
            mps.registerSubpage(node, _name, _node)
        elseif type(_node) == "string" then
            -- 默认转换为纯文本节点。
            mps.registerPlain(node, _name, _node)
        end
    end

    return node
end

return mps