-- -- This module will implement {{Navbox}} -- 这是该模板的更新测试版本。 -- local p = {} local navbar = require('Module:Navbar')._navbar local trim = mw.text.trim local function addNewline(s) -- 参数内容以这些字符开头时,添加一个换行符, -- 这是为了成功形成其格式。 if s:match('^[*:;#]') or s:match('^{|') then return '\n' .. s ..'\n' else return s end end local function index(t,...) local paras = {...} local ret = t for i, k in ipairs(paras) do if type(ret) =='table' then ret = ret[k] else return nil end end return ret end local function newindex(t,value,...) return tnewindex(t,value,{...}) end local function tnewindex(t,value,paras) if #paras <1 then return end local current = t local num = #paras for k,v in ipairs(paras) do if k==num then if type(current[v])~='table' then current[v] = value end return type(value)=='table' and current[v] or t else if type(current[v])~='table' then current[v] = {} end current = current[v] end end end local function getkeys(key,...) -- 识别单个数字或拆分逗号隔开的多个数字并转化为number, -- 非法的key会返回nil。 -- 拆分出的每个结果会夹着list和content成为一个表并传入tnewindex。 -- 可选参数会被加在表的最后。 local keys = {} for eachnum in key:gmatch '[^%- ]+' do --以横杠(减号)为分隔拆分数字 eachnum = tonumber(eachnum) if not eachnum then return nil end -- 遇到无法转换的内容,直接返回nil table.insert(keys,eachnum) table.insert(keys,'list') table.insert(keys,'content') end keys[#keys],keys[#keys-1] = nil -- 最后的两个list和content不一定需要 -- 需要时会作为可选参数传入 if #keys == 0 then return nil end for i, k in ipairs {...} do table.insert(keys,k) end return keys end -- 为便于维护,采用面向对象。 function p.new(paras) -- 创建一个新的navbox对象。 -- 创建时,其对应的html对象会被自动创建,也可以在参数中手动创建。 -- 创建一个子框表时,会给它设置好根框表。 local obj = {} local paras = paras or {} obj.rootself = paras.rootself or obj obj.data = paras.data or {} obj.args = paras.args or {} obj.level = paras.level or 0 -- 对象的级别,根框表为0,子框表为1 obj.isChild = paras.isChild -- 默认为nil obj.root = paras.root -- 通常为nil,如有需要会在渲染时准备好 return setmetatable(obj,{ __index=p }) end function p:renderRow(groupArgs, listArgs) -- 给定一个列的标题和文本,渲染这个列。 local listContent = listArgs.content if not listContent then return end local listStyle = listArgs.style local listClass = listArgs.class local groupContent = groupArgs.content local groupStyle = groupArgs.style local groupClass = groupArgs.class local data = self.data -- 根框表的数据。 local rootdata = self.rootself.data -- 对于子框表,其list的奇偶性存储在根框表中,而非子框表本身。 local isEven = self.rootself.isEven local root = self.root local row = root:tag 'tr' :addClass 'newstyle_navbox-row' if groupContent then local group = row:tag 'th' :addClass 'newstyle_navbox-group' :wikitext(addNewline(groupContent)) -- groupclass、groupstyle等不影响其子框表的group。 :addClass(index(data,'group','class')) -- 共同类,即args中的groupclass :addClass(groupClass) -- 单独类,比如group1class :cssText(index(data,'group','style')) -- 共同样式,比如groupstyle :cssText(groupStyle) -- 单独样式,比如group1style end local list = row:tag 'td' :addClass 'newstyle_navbox-list' if not groupContent then list :attr('colspan','2') :addClass 'newstyle_navbox-list-without-group' end if listArgs.isChild then --强制子框 list:addClass 'newstyle_navbox-list-with-subgroup' end if type(listContent) == 'string' then -- addNewline函数在前文中已定义 list:wikitext(addNewline(listContent)) -- listclass、liststyle等会影响其子框表的list。 -- 且如果一个list的内容是一个子框表, -- 则这个list不受listclass、liststyle的影响。 :addClass(index(rootdata,'list','class')) :cssText(index(rootdata,'list','style')) self.rootself.isEven = not isEven -- 交换奇偶性 if isEven then list :addClass 'newstyle_navbox-even' :addClass(index(rootdata,'even','class')) :cssText(index(rootdata,'even','style')) else list :addClass 'newstyle_navbox-odd' :addClass(index(rootdata,'odd','class')) :cssText(index(rootdata,'odd','style')) end elseif type(listContent) == 'table' then local subElement = list :addClass 'newstyle_navbox-list-with-subgroup' :tag 'table' :addClass 'newstyle_navbox' local subObj = self.new{ rootself = self, root = subElement, data = listContent, isChild = true, level = (self.level or 0) + 1 } subObj:render() end list :addClass(listClass) -- 单独类,如list1class :cssText(listStyle) -- 单独样式,如list1style return row end function p:renderSingleRow(rowArgs) -- 渲染标题、上方栏或下方栏 -- type可以是'title' 'above' 'below' local content = rowArgs.content if content then content = addNewline(content) else return end local class = rowArgs.class local style = rowArgs.style local rowtype = rowArgs.rowtype or 'unknown-rowtype' local root = self.root local level = rowArgs.level -- 适配title的navbar功能 if rowtype == 'title' and level == 0 then local navbarObj = rowArgs.navbarObj or '' content = navbarObj .. content root:addClass 'newstyle_navbox-with-navbar' end local row = root:tag 'tr' :addClass 'newstyle_navbox-row' :tag (rowtype=='title' and 'th' or 'td') :attr('colspan','2') :addClass('newstyle_navbox-' .. rowtype) :addClass(class) :cssText(style) :wikitext(content) if rowtype=='above' or rowtype=='below' then row:addClass 'newstyle_navbox-abovebelow' end return row end local suffixes = { content = true, style = true, class = true, ischild = true, [''] = true } function p:checkItem(k,v,data) -- 对于参数的一个键值对,对其键进行检查。 -- 符合特定条件的键会被以特定的方式“转录”到self:data中。 -- “转录”过程中需要用到前面定义的newindex。 data = data or self.data -- 首先检查一些固定的参数,检查成功即跳出函数。 if k=='name' then data[k]=v return end if k==1 and v=='child' then data.isChild = true end -- 以下均只检查字符串键。 if type(k)~='string' then return end -- 对于list前缀,如果其suffix不合法,后缀会作为一个完整的参数键传入list表。 -- 其实并没有用,因为“遗传”功能还没有开发。 local suffix = k:match "^list(%l+[%l%d%.%-]*)$" if suffix and not suffixes[suffix] then data.list = data.list or {} self:checkItem(suffix,v,data.list) return end -- 对下列这些前缀,检查其后缀(可以为空)。 -- 比如title、abovestyle、belowclass之类的。 for _, prefix in ipairs{'title','above','below', 'group','list','body','even','odd'} do -- 检查通用属性 local suffix=k:match('^' .. prefix .. '(%l*)$') -- 2021.6.14: +$ -- 这里的suffix可以是空值,或者style、class, -- 其他的值也有效,但是不起作用。 if suffix=='' then newindex(data,v,prefix,'content') return elseif suffix and suffixes[suffix] then -- 这里的suffix通常是style、class之类 newindex(data,v,prefix,suffix) return end -- 如果没有suffix进入下一轮循环 end -- 检查带有数字的变量。 local prefix, key, suffix = k:match '^([a-z]+)([0-9%.%-]+)(%l*)$' --2021.6.14:floatable -- 对于group和list,以及其他的类型,采取不同的数据加工方式 local isCell = (prefix=='group' or prefix=='list') -- 这里的prefix可以是group,list -- 这里的suffix可以是空白、class或style -- 这里的key可以是1、2、3或1-2、1-4这样的数字或多重数字。 if not suffix or not suffixes[suffix] then key,suffix = k:match '^list([%d%.%-]+)(%l+[%l%d%-%.]*)$' end if not key then return end if suffix=='' then suffix='content' end -- [[ local keys = getkeys(key) if not keys then return end local keys1, keys2, keys3 = getkeys(key,'list','content'), getkeys(key,prefix,suffix), getkeys(key,'list','content',prefix,suffix) -- mw.logObject(key) if not prefix then self:checkItem(suffix,v,tnewindex(data,{},keys1)) elseif isCell then tnewindex(data,v,keys2) else tnewindex(data,v,keys3) end -- ]] end function p:processArgs() -- 这里的args是frame中未经重写处理的args。 for k,v in pairs(self.args) do local key = k:match('^sub([%d%-%.]+)$') local keys = getkeys(key,'list','content') tnewindex(data,require 'Module:JSON'.decode(v),keys) end for k,v in pairs(self.args) do v = trim(v) if v and v~='' then self:checkItem(k,v) end end -- mw.logObject(self.data) end function p:render() -- 如果是通过args而非data创建的对象, -- 渲染之前务必记得processArgs,以将args“转录”到data中。 -- 因为渲染过程只认data不认args。 self.root = self.root or mw.html.create 'table' :addClass 'newstyle_navbox' local root = self.root local navbarObj local data = self.data or {} self.rootdata = self.rootself.data or {} self.isChild = self.isChild or data.isChild local isChild = self.isChild local level = self.level or 0 if isChild then root:addClass 'newstyle_navbox-subgroup' end root :addClass('newstyle_navbox-level-'..(level or 'unknown')) :addClass(index(data,'body','class')) :cssText(index(data,'body','style')) if level==0 then root:addClass 'stikitable hlist' local name = data.name if name then navbarObj = navbar{ mini = 1, title = name } end end -- 渲染标题。 self:renderSingleRow{ navbarObj = navbarObj, content = index(data,'title','content'), class = index(data,'title','class'), style = index(data,'title','style'), rowtype = 'title', level = level } -- 渲染上方框。 self:renderSingleRow{ content = index(data,'above','content'), class = index(data,'above','class'), style = index(data,'above','style'), rowtype = 'above', level = level } -- 渲染列表。这是重头戏。 -- 考虑到空缺参数问题,所以要先将键做成序列进行排序,再进行迭代。 local keys = {} for k,v in pairs(data) do -- keys是由data中所有整数键组成的序列 if type(k)=='number' then table.insert(keys,k) end end table.sort(keys) for i,k in pairs(keys) do local v = data[k] -- 上述9行语句其实就相当于 -- for k,v in pairs(data) do -- 但是考虑到断层参数的排序问题 -- 所以先进行人工排序再迭代 local groupContent = index(v,'group','content') local listContent = index(v,'list','content') local id = v.id or k -- 这个k其实没什么用 local groupClass = index(v,'group','class') local groupStyle = index(v,'group','style') local listClass = index(v,'list','class') local listStyle = index(v,'list','style') local isChild = index(v,'list','ischild') --强制性添加subgroup类,为了适配传统用法 self:renderRow({ content=groupContent, class=groupClass, style=groupStyle, level=level },{ content=listContent, class=listClass, style=listStyle, level=level, isChild=isChild }) end -- 渲染下方框。 self:renderSingleRow{ content = index(data,'below','content'), class = index(data,'below','class'), style = index(data,'below','style'), rowtype = 'below' } return root end function p._navbox(args) -- 通常第三方模块可以不必这样使用 -- 可以设置data然后再render local obj = p.new{args=args} obj:processArgs() local root = obj:render() return root end function p.direct(frame) -- 通过#invoke直接使用 return p._navbox(frame.args) end function p.json(frame) -- 先行配信 local obj = p.new {args = args} obj:processArgs() return require 'Module:JSON'.encode(obj.data) end return p