-- -- 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 index = function(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 newindex = function(t,value,...) local 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 return end current[v] = value else if type(current[v])~='table' then current[v] = {} end current = current[v] end end 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 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','class')) 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 newstyle_navbox-subgroup' 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 function p:checkItem(k,v) -- 对于参数的一个键值对,对其键进行检查。 -- 符合特定条件的键会被以特定的方式“转录”到self:data中。 -- “转录”过程中需要用到前面定义的newindex。 local data = 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 -- 对下列这些前缀,检查其后缀(可以为空)。 -- 比如title、abovestyle、belowclass之类的。 for _, prefix in ipairs{'title','above','below', 'group','list','body','even','odd'} do -- 检查通用属性 local suffix=k:match('^' .. prefix .. '([a-z]*)$') -- 2021.6.14: +$ -- 这里的suffix可以是空值,或者style、class, -- 其他的值也有效,但是不起作用。 if suffix=='' then newindex(data,v,prefix,'content') break elseif suffix then -- 这里的suffix通常是style、class之类 newindex(data,v,prefix,suffix) break end -- 如果没有suffix进入下一轮循环 end -- 检查带有数字的变量。 local prefix, key, suffix = k:match '^([a-z]+)([0-9%.%-]+)([a-z]*)$' --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 key then return end if suffix=='' then suffix='content' end -- 如果是匹配单个数字,比如list2style、group4class。 local id=tonumber(key) if id then if isCell then newindex(data,v,id,prefix,suffix) else newindex(data,v,id,'list','content',prefix,suffix) end return end -- 如果是匹配双个数字(数字用连字符隔开,下同), -- 比如list2-3style、group2-3class。 -- 应该可以直接匹配数值的,后续会继续优化。 local id2 id,id2 = key:match '^([0-9]+%.?[0-9]*)%-([0-9]+%.?[0-9]*)$' id,id2 = tonumber(id),tonumber(id2) if id and id2 then if isCell then newindex(data,v,id,'list','content',id2,prefix,suffix) else newindex(data,v,id,'list','content',id2,'list','content',prefix,suffix) end return end -- 如果是匹配三个,比如list2-3-5style、group1-5-4class。 local id3 id,id2,id3 = key:match '^([0-9]+%.?[0-9]*)%-([0-9]+%.?[0-9]*)%-([0-9]+%.?[0-9]*)$' id,id2,id3 = tonumber(id),tonumber(id2),tonumber(id3) if id and id2 and id3 and isCell then newindex(data,v,id,'list','content',id2,'list','content', id3,prefix,suffix) end end function p:processArgs() -- 这里的args是frame中未经重写处理的args。 for k,v in pairs(self.args) do v = trim(v) if v and v~='' then self:checkItem(k,v) end end 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') self:renderRow({ content=groupContent, class=groupClass, style=groupStyle, level=level },{ content=listContent, class=listClass, style=listStyle, level=level }) 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.navbox(frame) -- 通过模板使用 return p._navbox(frame:getParent().args) end return p