--
-- 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