Module:Infobox: Difference between revisions

From Soyjak Wiki, The Free Soycyclopedia
Jump to navigationJump to search
(test, this is probably very heavy and has a lot of unnecessary code)
 
(merge hlist here)
Line 1: Line 1:
--[=[
local p = {}
-- For documentation, see [[Module:Infobox/doc]]
local args = {}
--]=]
local origArgs = {}
 
local root
-- <nowiki>
local empty_row_categories = {}
local Infobox = {}
local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]'
Infobox.__index = Infobox
local has_rows = false
Infobox.__tostring = Infobox.tostring
local lists = {
 
plainlist_t = {
-- Edit button for unknown params
patterns = {
local editbutton = require('Module:Edit button')
'^plainlist$',
local var = mw.ext.VariablesLua
'%splainlist$',
local edit = editbutton("'''?''' (edit)")
'^plainlist%s',
 
'%splainlist%s'
-- Page title
},
local pagename = mw.title.getCurrentTitle().fullText
found = false,
 
styles = 'Plainlist/styles.css'
-- map of flags to html tags used by Infobox.addRow()
},
-- let's only define it once, since :addRow() is used multiple times per module
hlist_t = {
local tagmap = {
patterns = {
tr = 'tr',
'^hlist$',
th = 'th',
'%shlist$',
td = 'td',
'^hlist%s',
argh = 'th',
'%shlist%s'
argd = 'td'
},
found = false,
styles = 'Hlist/styles.css'
}
}
}


--[=[
local function has_list_class(args_to_check)
-- Standardised functions
for _, list in pairs(lists) do
-- called as string with defineParams
if not list.found then
--]=]
for _, arg in pairs(args_to_check) do
 
for _, pattern in ipairs(list.patterns) do
-- Standardised "has content" function
if mw.ustring.find(arg or '', pattern) then
function hasContent(arg, default)
list.found = true
-- Return arg if any non-whitespace character is found
break
return string.match(arg or '','%S') and arg or default
end
end
end
 
if list.found then break end
 
-- Standardised "name" function
function subjectName(arg)
return string.match(arg or '','%S') and arg or nil
end
 
-- Create a standardised release function, since so many pages use it
-- Turns release and update into a single parameter
function releaseUpdate(release, update)
if not Infobox.isDefined(release) then
return nil
end
if string.lower(release) == 'no' then
return 'N/A'
end
if not Infobox.isDefined(update) then
return string.format('%s (Update unknown)',release)
end
if string.lower(update) == 'no' then
return release
end
return string.format('%s ([[Update:%s|Update]])', release, update)
end
 
-- Standardised image function
function image(img)
if img and img:find('%S') then
return img
else
return nil
end
end
 
-- Standardised numbers
function numbers(num)
num = string.gsub(num or '',',','')
return tonumber(num)
end
 
-- Wrap content with line breaks if it contains list-like wiki syntax
function wrapContent(content)
if type(content) == "string" then
local firstListItem = math.min(content:find('^[*#]') or math.huge, content:find('\n[*#]') or math.huge)
if firstListItem ~= math.huge then
local suffix = content:find('\n[*#][^\n]+$') and '\n' or ''
content = content:sub(1, firstListItem - 1) .. '\n' .. content:sub(firstListItem) .. suffix
end
end
return content
end
 
-- map of names to pre-defined functions, used by Infobox:defineParams
local func_map = {
name = subjectName,
release = { name = releaseUpdate, params = { 'release', 'update' }, flag = 'p' },
removal = { name = releaseUpdate, params = { 'removal', 'removalupdate' }, flag = 'p' },
has_content = hasContent,
hasContent = hasContent,
image = image,
numbers = numbers,
}
 
-- used to fill nil params in switching sections
-- this message isn't kidding
-- If you see this message anywhere outside of this code
-- (including inside switchfo box data)
-- report it
local nil_param = 'UH OH YOU SHOULDN\'T SEE THIS!'
 
-- In case the nil_param is needed outside of this module
-- give it an easy way to be accessed
function Infobox.nilParam()
return nil_param
end
 
-- switch infobox globals
local LINE_WIDTH = 300
local MAX_LINES = 2
local DEFAULT_MAX_BUTTONS = 6
-- calculate with width of a switch infobox button
-- potential @TODO: rework to use actual character widths
function button_width(label)
local PX_PER_CHAR = 6
local PX_PAD_MAR = 24
return string.len(label) * PX_PER_CHAR + PX_PAD_MAR
end
 
Infobox.splitpoint = '&&SPLITPOINT&&'
 
-- quick test to see if a value is considered nil
function Infobox.isDefined(arg)
if arg == nil then
return false
end
 
if type(arg) == 'string' then
if arg == nil_param then
return false
elseif arg:find('%S') then
if arg:find('action=edit') then
return false
else
return true
end
end
else
return false
end
end
end
return true
end
--[[
Infobox class
-- args : parameters from frame to pass through
-- Sets a meta table and creates a <div> tag wrapper
-- other fields are initialised in other functions
--]]
function Infobox.new(args)
local obj = setmetatable({
args = args, -- parameters (uncleaned)
rargs = {}, -- parameters (cleaned)
params = {}, -- parameters mapped to functions
paramnames = {}, -- parameter names
dupeable = {}, -- parameters that are allowed to have duplicated switch data
addrswibclass = true,
switchfo = false, -- switch infobox? or not?
switchfoattr = {}, -- switch data class changes
maxbuttons = DEFAULT_MAX_BUTTONS, -- maximum number of buttons before switching becomes a menu
switch_tag = '', -- switchfo data
switch_buttons_tag = '', -- switchfo buttons
custom_buttons = false,
smw_error_tag = '',
rtable = nil, -- returned infobox table
labels = nil, -- returned labels
_smw = {}, -- semantic mediawiki data
_smwOne = {}, -- semantic mediawiki data part 2
_smwSubobject = {}, -- semantic mediawiki data part 3
_smwSubobjectName = nil, -- semantic mediawiki data part 3.5
_smwElement = {}, -- semantic mediawiki data part 4
set_default_version_smw = false, -- whether to set [[Property:Default version]]
setSMWElement = true,
suppressAllSMW = false,
suppressVersionSMW = {},
versions = -1, -- number of switch versions (-1 is uncalculated)
infoboxname = nil, -- template name
appendStrs = {},
bottomlinks = { -- template bottom links
links = {
{ 'Template talk:%s', 'talk' },
{ 'Template:%s', 'view' }
},
colspan = 2
},
catdata = {}, -- meta category data
catlist = {}, -- defined table of category names (strings)
__finished = false, -- infobox status
},
Infobox)
return obj
end
--[[
Toggles the addition of infobox class
use before :create()
noop if not a boolean
--]]
function Infobox:setAddRSWInfoboxClass(bool)
if type(bool) == 'boolean' then
self.addrswibclass = bool
end
end
end
end


--[[
local function fixChildBoxes(sval, tt)
Creates an infobox
local function notempty( s ) return s and s:match( '%S' ) end
-- If Infobox:maxVersions() has not been run, it will be run here
-- If the infobox should be a switch infobox, all labels will be added
-- Creates a wikitable that will be the infobox
THIS SHOULD BE DONE AFTER ADDING AND CLEANING PARAMETERS
--]]
function Infobox:create()
-- Run to find if this is a switch infobox and if so, how many boxes
if self.versions == -1 then
self:maxVersion()
end
-- Run if switch infobox
if notempty(sval) then
if self.switchfo then
local marker = '<span class=special_infobox_marker>'
-- Buttons wrapper
local s = sval
-- Hidden by default, unhidden by javascript
-- start moving templatestyles and categories inside of table rows
self.switch_buttons_tag = mw.html.create('div')
local slast = ''
:addClass('infobox-buttons')
while slast ~= s do
 
slast = s
-- default version to immediately switch to via js
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1')
local defv = tonumber(self.args.defver)
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1')
if defv and defv <= self.versions then -- you troll, don't try to show something that isn't there
self.switch_buttons_tag:attr('data-default-version',defv)
end
end
 
-- end moving templatestyles and categories inside of table rows
local numlines = 1
s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1')
local width_working = 0
s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker)
local total_width = 0
if s:match(marker) then
local buttons = {}
s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '')
-- Add individual buttons to the wrapper
s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1')
for i=1,self.versions do
s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1')
local wid = button_width(self.labels[i] or i)
s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1')
width_working  = width_working + wid
s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1')
total_width = total_width + wid
s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
if width_working > LINE_WIDTH then
s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
numlines = numlines + 1
s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1')
width_working = wid
s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1')
end
local b = mw.html.create('span')
:attr('data-switch-index',tostring(i))
-- space to underscore
:attr('data-switch-anchor','#'..string.gsub(self.labels[i] or i,' ','_'))
:addClass('button')
:wikitext(self.labels[i] or i)
table.insert(buttons, {b, wid})
end
end
if s:match(marker) then
local best = {-1, 100000}
local subcells = mw.text.split(s, marker)
if (numlines > 1) and (numlines <= MAX_LINES) then
s = ''
-- attempt to balance line widths
for k = 1, #subcells do
local w_s, w_e = 0,total_width
if k == 1 then
for i = 1,#buttons-1 do
s = s .. subcells[k] .. '</' .. tt .. '></tr>'
w_s = w_s + buttons[i][2]
elseif k == #subcells then
w_e = w_e - buttons[i][2]
local rowstyle = ' style="display:none"'
if w_s > LINE_WIDTH then
if notempty(subcells[k]) then rowstyle = '' end
-- w_s only increases, so we're done once it exceeds the width
s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' ..
break
subcells[k]
end
elseif notempty(subcells[k]) then
if w_e <= LINE_WIDTH then
if (k % 2) == 0 then
-- w_e only decreases, so just continue if it exceeds line
s = s .. subcells[k]
local diff = math.abs(w_s - w_e)
else
if diff < best[2] then
s = s .. '<tr><' .. tt .. ' colspan=2>\n' ..
best = { i, diff }
subcells[k] .. '</' .. tt .. '></tr>'
end
end
end
end
end
if best[1] == -1 then
best = { math.floor(#buttons/2), 100000 }
end
end
end
end
for i,v in ipairs(buttons) do
-- the next two lines add a newline at the end of lists for the PHP parser
self.switch_buttons_tag:node(v[1])
-- [[Special:Diff/849054481]]
if i == best[1] then
-- remove when [[:phab:T191516]] is fixed or OBE
self.switch_buttons_tag:tag('br')
s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n')
end
s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n')
end
s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1')
s = mw.ustring.gsub(s, '^(%{%|)', '\n%1')
-- Used by JavaScript to turn the buttons into a menu list if too many variants
return s
if self.versions > self.maxbuttons or numlines > MAX_LINES then
else
self.switch_buttons_tag:addClass('infobox-buttons-select')
return sval
end
self.switch_buttons_tag:done()
end
end
end


-- Create infobox table
-- Cleans empty tables
self.rtable = mw.html.create('table')
local function cleanInfobox()
if self.addrswibclass then
root = tostring(root)
self.rtable:addClass('infobox')
if has_rows == false then
root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '')
end
end
-- Add necessary class if switch infobox
if self.switchfo then
self.rtable:addClass('infobox-switch')
end
end
end


-- Defines an infobox name ({{Template:arg}})
-- Returns the union of the values of two tables, as a sequence.
-- Used to create a link at the bottom of pages
local function union(t1, t2)
function Infobox:defineName(arg)
self.infoboxname = arg
end


-- Defines the bottom links of the infobox
local vals = {}
-- pass a table whose elements are tables that define a link and a label
for k, v in pairs(t1) do
-- {
vals[v] = true
-- { 'link', 'label },
end
-- ...
for k, v in pairs(t2) do
-- }
vals[v] = true
-- The template name can be substituted into the tables using '%s'
end
-- If we wanted Template:InFooBar to link to it's /doc page with a "doc" label:
local ret = {}
-- { ...
for k, v in pairs(vals) do
-- { 'Template:%s/doc', 'doc' },
table.insert(ret, k)
-- ... }
-- The template's name can only be called 5 times
function Infobox:defineLinks(arg)
if type(arg) == 'table' then
if arg.colspan then
self.bottomlinks.colspan = arg.colspan
end
if arg.links then
if type(arg.links) == 'table' then
self.bottomlinks.links = arg.links
end
end
if arg.hide then
self.bottomlinks.hide = arg.hide
end
end
end
return ret
end
end


-- Change max number of buttons before switching to menu
-- Returns a table containing the numbers of the arguments that exist
-- defaults to 5
-- for the specified prefix. For example, if the prefix was 'data', and
-- MUST BE RUN BEFORE :create()
-- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}.
function Infobox:setMaxButtons(arg)
local function getArgNums(prefix)
-- if not a number, just go back to default
local nums = {}
self.maxbuttons = tonumber(arg) or DEFAULT_MAX_BUTTONS
for k, v in pairs(args) do
local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$')
if num then table.insert(nums, tonumber(num)) end
end
table.sort(nums)
return nums
end
end


--[[
-- Adds a row to the infobox, with either a header cell
Add parameters functions
-- or a label/data cell combination.
All parameters should be tables
local function addRow(rowArgs)
The first parameter defines the type of cell to create
-- th : <th>
if rowArgs.header and rowArgs.header ~= '_BLANK_' then
-- td : <td>
has_rows = true
-- argh : <th>
has_list_class({ rowArgs.rowclass, rowArgs.class, args.headerclass })
-- argd : <td>
The second parameter defines what is inside the tag
root
-- th | th : text passed
:tag('tr')
-- argh | argd : parameter with the name passed
:addClass(rowArgs.rowclass)
Additional named parameters can be used to add any styling or attributes
:cssText(rowArgs.rowstyle)
-- attr : mw.html:attr({ arg1 = '1', ... })
:tag('th')
-- css : mw.html:css({ arg1 = '1', ...)
:attr('colspan', '2')
-- class : mw.html:addClass('arg')
:addClass('infobox-header')
---- class also supports a table of values, even though mw.html:addClass() does not
:addClass(rowArgs.class)
-- rowspan : mw.html:attr('rowspan',arg)
:addClass(args.headerclass)
-- colspan : mw.html:attr('colspan',arg)
-- @deprecated next; target .infobox-<name> .infobox-header
-- title : mw.html:attr('title',arg)
:cssText(args.headerstyle)
Example:
:cssText(rowArgs.rowcellstyle)
ipsobox:addRow( { 'th' , 'Header', title = 'Title' },
:wikitext(fixChildBoxes(rowArgs.header, 'th'))
{ 'argh', 'arg1', class = 'parameter' } })
if rowArgs.data then
produces:
root:wikitext(
<tr><th title="Title">Header</th><th class="parameter">args.arg1</th></tr>
'[[Category:Pages using infobox templates with ignored data cells]]'
 
)
adding it to the infobox table of ipsobox
 
Cells defined as 'argh' and 'argd' will automatically have data-attr-param="" added, and defined as the passed argument if the infobox in creation is defined as a switch infobox
 
The row itself may be modified with metadata using the named index at "meta"
-- meta.addClass : mw.html:addClass('arg')
-- this function currently only supports a single string
--]]
function Infobox.addRow(box, ...)
-- New row to add
local args = ...
local _row = box.rtable:tag('tr')
-- For each member of tags
for i, v in ipairs(args) do
-- map tag name to appropriate tag, default to <td>
local _cell = _row:tag(tagmap[v.tag] or 'td')
 
-- mw.html:attr() and mw.html:css() both accept table input
-- colspan, rowspan, title will be quick ways to access attr
-- these functions also do all the necessary work
if v.attr then
_cell:attr(v.attr)
end
end
if v.colspan then
elseif rowArgs.data and rowArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
_cell:attr('colspan',v.colspan)
has_rows = true
end
has_list_class({ rowArgs.rowclass, rowArgs.class })
if v.rowspan then
_cell:attr('rowspan',v.rowspan)
local row = root:tag('tr')
end
row:addClass(rowArgs.rowclass)
if v.title then
row:cssText(rowArgs.rowstyle)
_cell:attr('title',v.title)
if rowArgs.label then
end
row
if v.css then
:tag('th')
_cell:css(v.css)
:attr('scope', 'row')
:addClass('infobox-label')
-- @deprecated next; target .infobox-<name> .infobox-label
:cssText(args.labelstyle)
:cssText(rowArgs.rowcellstyle)
:wikitext(rowArgs.label)
:done()
end
end


-- if class is a string, it can be added directly
local dataCell = row:tag('td')
-- if a table, add every value
dataCell
-- mw.html:addClass() doesn't function with tables
:attr('colspan', not rowArgs.label and '2' or nil)
-- so iterate over the class names here and add them individually
:addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data')
if v.class then
:addClass(rowArgs.class)
if type(v.class) == 'string' then
-- @deprecated next; target .infobox-<name> .infobox(-full)-data
_cell:addClass(v.class)
:cssText(rowArgs.datastyle)
elseif type(v.class) == 'table' then
:cssText(rowArgs.rowcellstyle)
for _, w in ipairs(v.class) do
:wikitext(fixChildBoxes(rowArgs.data, 'td'))
_cell:addClass(w)
else
end
table.insert(empty_row_categories, rowArgs.data or '')
end
end
 
-- if the cell is a normal th or td, add the exact argument passed
if v.tag == 'th' or v.tag == 'td' then
_cell:wikitext(wrapContent(v.content))
-- if defined with "arg", add the argument with name passed
elseif v.tag == 'argh' or v.tag == 'argd' then
local content = box.rargs[v.content]
-- if the requested parameter doesn't exist whatsoever, just return a blank string
if not content then
content = ''
-- If switches exist, first attempt to use the version1 values
elseif content.switches then
if content.switches[1] ~= nil_param then
content = content.switches[1] or ''
else
content = content.d or ''
end
-- fallback to default value
else
content = content.d or ''
end
 
_cell:wikitext(wrapContent(content))
 
-- add necessary attribute for switch infoboxes
if box.switchfo then
_cell:attr('data-attr-param',v.content)
end
end
end
end
end


-- not that meta
local function renderTitle()
-- allow classes to be defined on the whole row
if not args.title then return end
-- okay, sort of meta
if args.meta then
if args.meta.addClass then
_row:addClass(args.meta.addClass)
end
end


return box
has_rows = true
has_list_class({args.titleclass})
root
:tag('caption')
:addClass('infobox-title')
:addClass(args.titleclass)
-- @deprecated next; target .infobox-<name> .infobox-title
:cssText(args.titlestyle)
:wikitext(args.title)
end
end


function Infobox.customButtonPlacement(box,arg)
local function renderAboveRow()
box.custom_buttons = arg
if not args.above then return end
return box
end


-- Choose whether to set [[Property:Default version]]
has_rows = true
-- Defaults to false.
has_list_class({ args.aboveclass })
function Infobox.setDefaultVersionSMW(box, arg)
box.set_default_version_smw = arg
root
return box
:tag('tr')
:tag('th')
:attr('colspan', '2')
:addClass('infobox-above')
:addClass(args.aboveclass)
-- @deprecated next; target .infobox-<name> .infobox-above
:cssText(args.abovestyle)
:wikitext(fixChildBoxes(args.above,'th'))
end
end


function Infobox.addButtonsRow(box, args)
local function renderBelowRow()
if box.switchfo then
if not args.below then return end
box.custom_buttons = true
local _row = box.rtable:tag('tr')
:addClass('infobox-switch-buttons-row')
:tag('td')
:addClass('infobox-switch-buttons')
:attr('colspan', args.colspan)
:node(box.switch_buttons_tag)
end
return box
end
function Infobox.addButtonsCaption(box)
if box.switchfo then
box.custom_buttons = true
local _row = box.rtable:tag('caption')
:addClass('infobox-switch-buttons-caption')
:node(box.switch_buttons_tag)
end
return box
end


--[[
has_rows = true
-- adds a blank row of padding spanning the given number of columns
has_list_class({ args.belowclass })
--]]
function Infobox.pad(box, colspan, class)
root
local tr = box:tag('tr')
:tag('tr')
:tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding')
:tag('td')
:done()
:attr('colspan', '2')
if class then
:addClass('infobox-below')
tr:addClass(class)
:addClass(args.belowclass)
end
-- @deprecated next; target .infobox-<name> .infobox-below
tr:done()
:cssText(args.belowstyle)
return box
:wikitext(fixChildBoxes(args.below,'td'))
end
end


--[[
local function addSubheaderRow(subheaderArgs)
-- functions the same as mw.html:wikitext() on the wrapper
if subheaderArgs.data and
-- Should only be used for categories really
subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
--]]
has_rows = true
function Infobox.wikitext(box, arg)
has_list_class({ subheaderArgs.rowclass, subheaderArgs.class })
box.rtable:wikitext(arg)
return box
local row = root:tag('tr')
end
row:addClass(subheaderArgs.rowclass)


--[[
local dataCell = row:tag('td')
-- Adds the specified item(s) to the end of the infobox, outside of the table
dataCell
-- items are concatenated together with an empty space
:attr('colspan', '2')
--]]
:addClass('infobox-subheader')
function Infobox.append(box, ...)
:addClass(subheaderArgs.class)
for i,v in ipairs({...}) do
:cssText(subheaderArgs.datastyle)
table.insert(box.appendStrs, v)
:cssText(subheaderArgs.rowcellstyle)
:wikitext(fixChildBoxes(subheaderArgs.data, 'td'))
else
table.insert(empty_row_categories, subheaderArgs.data or '')
end
end
return box
end
end


--[[
local function renderSubheaders()
-- Adds a caption to the infobox
if args.subheader then
-- defaults to the pagename
args.subheader1 = args.subheader
-- or the default argument if defined
--]]
function Infobox.caption(box)
-- default to the article's name
local name = pagename
-- first see if the name parameter exists
if box.rargs.name then
-- then try the default
if box.rargs.name.d then
name = box.rargs.name.d
-- then look for swithes
elseif box.rargs.name.switches then
-- then look at version 1
if box.rargs.name.switches[1] ~= nil_param then
name = box.rargs.name.switches[1]
end
end
end
end
 
if args.subheaderrowclass then
local caption = box.rtable:tag('caption')
args.subheaderrowclass1 = args.subheaderrowclass
:wikitext(name)
 
-- add necessary attribute for switch infoboxes
if box.switchfo then
caption:attr('data-attr-param','name')
end
end
 
local subheadernums = getArgNums('subheader')
return box
for k, num in ipairs(subheadernums) do
end
addSubheaderRow({
 
data = args['subheader' .. tostring(num)],
--[[
-- @deprecated next; target .infobox-<name> .infobox-subheader
-- Functions for styling the infobox
datastyle = args.subheaderstyle,
-- works the same as the respective mw.html functions
rowcellstyle = args['subheaderstyle' .. tostring(num)],
--]]
class = args.subheaderclass,
-- attr
rowclass = args['subheaderrowclass' .. tostring(num)]
function Infobox.attr(box, arg)
})
box.rtable:attr(arg)
return box
end
 
-- css
function Infobox.float(box,float)
box.rtable:css('float',float)
return box
end
 
function Infobox.css(box, ...)
box.rtable:css(...)
return box
end
 
-- addClass
function Infobox.addClass(box, arg)
box.rtable:addClass(arg)
return box
end
 
-- Much like Infobox.addClass, but adds multiple classes
function Infobox.addClasses(box, ...)
for _, v in ipairs(...) do
box.rtable:addClass(box)
end
end
return box
end
end


--[[
local function addImageRow(imageArgs)
Add tags directly to the infobox table
Use sparingly
Returns the tag created rather than the entire box
Which is an mw.html object
Further uses of :tag() will be mw.html.tag, rather than Infobox.tag
As such, Infobox:addRow() cannot be used afterwards without restating the infobox as the object
--]]
function Infobox.tag(box, arg)
return box.rtable:tag(arg)
end


--[[
if imageArgs.data and
Allows the infobox to use Semantic Media Wiki and give parameters properties
imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
Pass a table to this function to map parameter names to properties


Calling syntax:
has_rows = true
-- {{#show:page|?property}}:
has_list_class({ imageArgs.rowclass, imageArgs.class })
-- "<property>" - unqualified and without a number will display the default value
-- "<property#>" - with a number will show the switch data from that index
local row = root:tag('tr')
-- "all <property>" - adding all will display every unique value in a comma separated list
row:addClass(imageArgs.rowclass)


Properties initiated in Infobox:finish()
local dataCell = row:tag('td')
--]]
dataCell
function Infobox:useSMW(arg)
:attr('colspan', '2')
if type(arg) == 'table' then
:addClass('infobox-image')
for w, v in pairs(arg) do
:addClass(imageArgs.class)
self._smw[w] = v
:cssText(imageArgs.datastyle)
end
:wikitext(fixChildBoxes(imageArgs.data, 'td'))
else
table.insert(empty_row_categories, imageArgs.data or '')
end
end
end
end
--[[
As above, but only assigns to "<property>", which will act like "all <property>" - "<property>#" not present


Properties initiated in Infobox:finish()
local function renderImages()
--]]
if args.image then
function Infobox:useSMWOne(arg)
args.image1 = args.image
if type(arg) == 'table' then
for w, v in pairs(arg) do
self._smwOne[w] = v
end
end
end
end
if args.caption then
--[[
args.caption1 = args.caption
    Set up the infobox to set properties in a SMW subobject. This will create a subobject for each version
    - if there is only one version, it will put the properties directly on to the page, like useSMWOne
 
Properties initiated in Infobox:finish()
--]]
function Infobox:useSMWSubobject(arg)
if type(arg) == 'table' then
for w, v in pairs(arg) do
self._smwSubobject[w] = v
end
end
end
end
local imagenums = getArgNums('image')
 
for k, num in ipairs(imagenums) do
function Infobox:useSMWElement(arg)
local caption = args['caption' .. tostring(num)]
if type(arg) == 'table' then
local data = mw.html.create():wikitext(args['image' .. tostring(num)])
for w, v in pairs(arg) do
if caption then
self._smwElement[w] = v
data
:tag('div')
:addClass('infobox-caption')
-- @deprecated next; target .infobox-<name> .infobox-caption
:cssText(args.captionstyle)
:wikitext(caption)
end
end
self.setSMWElement = true
addImageRow({
data = tostring(data),
-- @deprecated next; target .infobox-<name> .infobox-image
datastyle = args.imagestyle,
class = args.imageclass,
rowclass = args['imagerowclass' .. tostring(num)]
})
end
end
end
end
--[[
Finishing function
-- Finishes the return, adding necessary final tags
--]]
function Infobox:finish()
local currentNamespace = mw.title.getCurrentTitle().namespace
-- 0 = mainspace, 4 = RuneScape
local onContentNamespace = currentNamespace == 0 or currentNamespace == 4
-- Don't finish twice
if self.__finished then
return
end
-- Add switch infobox resources
if self.switchfo then
self.rtable:attr('data-resource-class', '.infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_'))
-- Wrapper tag, hidden
self.switch_tag = mw.html.create('div')
:addClass('infobox-switch-resources')
:addClass('infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_'))
:addClass('hidden')
for _, v in ipairs(self.paramnames) do
local param = self.rargs[v]
local default_value = param.d or edit
-- Parameters may not have any switches data, those are ignored
local switchattr = self.switchfoattr[v]
-- Parameter data wrapper
local res_span = self.switch_tag:tag('span')
:attr('data-attr-param',v)
-- Child for default value
local def = res_span:tag('span')
:attr('data-attr-index',0)
:wikitext(tostring(default_value))
-- Switch classes
if switchattr then
def:attr('data-addclass',switchattr.d)
end
def:done()
if param.switches then
-- Add all switches, ignore those defined as nil
for i, w in ipairs(param.switches) do
if w ~= nil_param and w ~= nil and w ~= default_value then
local _w = res_span:tag('span')
:attr('data-attr-index',i)
:wikitext(tostring(w))
-- Switch classes
if switchattr and switchattr.switches then
_w:attr('data-addclass',switchattr.switches[i])
elseif switchattr then
mw.logObject({string.format("Expected switches for `%s` but got none:", v), switchattr})
end
_w:done()
end
end
res_span:done()
end
end
-- Add a tracking category for pages that have more than 1 version
if onContentNamespace and self.versions > 1 then
-- version count data
self.switch_tag:wikitext('[[Category:Pages that contain switch infobox data]]')


if not self.suppressAllSMW then
-- When autoheaders are turned on, preprocesses the rows
self.switch_tag:tag('span')
local function preprocessRows()
:wikitext(string.format('Versions: [[Version count::%s]]',self.versions))
if not args.autoheaders then return end
:done()
 
-- set default version smw
local defver = tonumber(self.args.defver) or 1
local default_subobject_value = self.args['smwname'..defver] or self.args['version'..defver]
if default_subobject_value and self.set_default_version_smw then
-- Only take first subobject if multiple are defined using "¦"
local default_subobject_name = default_subobject_value:match("[^¦]+")
self.switch_tag:tag('span')
:wikitext(string.format('Default version: [[Default version::%s]]',default_subobject_name))
end
end
end
 
self.switch_tag:done()
end
-- smw data
local rownums = union(getArgNums('header'), getArgNums('data'))
if onContentNamespace and not self.suppressAllSMW then
table.sort(rownums)
-- members smw display, yes --> true; no --> false; other --> unknown
local lastheader
local function smwMembers(smw_arg)
for k, num in ipairs(rownums) do
local smw_argv = string.lower(smw_arg or '')
if args['header' .. tostring(num)] then
if smw_argv == 'yes' then
if lastheader then
return 'true'
args['header' .. tostring(lastheader)] = nil
elseif smw_argv == 'no' then
return 'false'
else
return 'unknown'
end
end
 
-- release date smw display
local function smwRelease(smw_arg)
local _d,_m,_y = string.match(smw_arg or '', '%[%[(%d%d?) (%a+)%]%] %[%[(%d%d%d%d)%]%]')
if _d == nil then
return nil
end
return table.concat({_d,_m,_y},' ')
end
 
-- default, just return the text
local function smwDefault(smw_arg)
if smw_arg ~= nil_param and smw_arg ~= edit then
return smw_arg
else
return 'unknown'
end
end
 
local smw_to_func = {
members = smwMembers,
release = smwRelease,
removal = smwRelease,
default = smwDefault
}
local smw_data_arr = {}
 
-- custom properties
for w, v in pairs(self._smw) do
-- only needed to give special formatting to release
-- and to make members true/false
local smwfunc = smw_to_func[w] or smw_to_func.default
 
local curarg = self.rargs[w]
if curarg then
local _arg = curarg.d
local argdefault = _arg
if _arg == edit then
argdefault = 'unknown'
else
local _x = mw.text.split(tostring(_arg), Infobox.splitpoint, true)
if not smw_data_arr[v] then
smw_data_arr[v] = {}
end
if not smw_data_arr['All '..v] then
smw_data_arr['All '..v] = {}
end
for _,y in ipairs(_x) do
local temp_smw_data = smwfunc(y)
table.insert(smw_data_arr[v], temp_smw_data)
table.insert(smw_data_arr['All '..v], temp_smw_data)
end
end
 
if curarg.switches then
local _args = {}
 
for x_i, x in ipairs(curarg.switches) do
if not self.suppressVersionSMW[x_i] then
if x ~= nil_param then
table.insert(_args,x)
else
table.insert(_args,argdefault or nil_param)
end
end
end
 
for i, x in ipairs(_args) do
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true)
if not smw_data_arr[v..i] then
smw_data_arr[v..i] = {}
end
if not smw_data_arr['All '..v] then
smw_data_arr['All '..v] = {}
end
for _,y in ipairs(_x) do
local temp_smw_data = smwfunc(y)
table.insert(smw_data_arr[v..i], temp_smw_data)
table.insert(smw_data_arr['All '..v], temp_smw_data)
end
end
end
end
end
-- if one version, put smwSubobject into smwOne and just do that
if self.versions < 2 and not self._smwSubobjectName then
for w,v in pairs(self._smwSubobject) do
if not self._smwOne[w] then
self._smwOne[w] = v
elseif type(self._smwOne[w]) == 'table' then
table.insert(self._smwOne[w], v)
else
self._smwOne[w] = { self._smwOne[w], v }
end
end
end
for w, _v in pairs(self._smwOne) do
-- only needed to give special formatting to release
-- and to make members true/false
local smwfunc = smw_to_func[w] or smw_to_func.default
 
local curarg = self.rargs[w]
if curarg then
local _arg = curarg.d
local argdefault = _arg
if _arg == edit then
argdefault = 'unknown'
end
if curarg.switches then
local _args = {}
 
for x_i, x in ipairs(curarg.switches) do
if not self.suppressVersionSMW[x_i] then
if x ~= nil_param then
table.insert(_args,x)
else
table.insert(_args,argdefault or nil_param)
end
end
end
 
for i, x in ipairs(_args) do
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true)
for _,y in ipairs(_x) do
local temp_smw_data = smwfunc(y)
for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do
if not smw_data_arr[v] then
smw_data_arr[v] = {}
end
table.insert(smw_data_arr[v], temp_smw_data)
end
end
end
else
if Infobox.isDefined(_arg) then
local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true)
for _,y in ipairs(_targ) do
local temp_smw_data = smwfunc(y)
for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do
if not smw_data_arr[v] then
smw_data_arr[v] = {}
end
table.insert(smw_data_arr[v], temp_smw_data)
end
end
end
end
end
end
local smw_data_arr_elem = {}
for w, v in pairs(self._smwElement) do
-- only needed to give special formatting to release
-- and to make members true/false
local smwfunc = smw_to_func[w] or smw_to_func.default
 
local curarg = self.rargs[w]
if curarg then
local _arg = curarg.d
local argdefault = _arg
if _arg == edit then
argdefault = 'unknown'
end
if curarg.switches then
local _args = {}
 
for x_i, x in ipairs(curarg.switches) do
if not self.suppressVersionSMW[x_i] then
if x ~= nil_param then
table.insert(_args,x)
else
table.insert(_args,argdefault or nil_param)
end
end
end
 
for i, x in ipairs(_args) do
if Infobox.isDefined(x) then
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true)
for _,y in ipairs(_x) do
local temp_smw_data = smwfunc(y)
if not smw_data_arr_elem[v] then
smw_data_arr_elem[v] = {}
end
table.insert(smw_data_arr_elem[v], temp_smw_data)
end
end
end
else
if Infobox.isDefined(_arg) then
local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true)
for _,y in ipairs(_targ) do
local temp_smw_data = smwfunc(y)
if not smw_data_arr_elem[v] then
smw_data_arr_elem[v] = {}
end
table.insert(smw_data_arr_elem[v], temp_smw_data)
end
end
end
end
end
-- if is a switchfo, setup for subobjects
local smw_data_arr_subobj = {}
if self._smwSubobjectName then
for i,k in ipairs(self._smwSubobjectName) do
if not self.suppressVersionSMW[i] then
local subobj_data = {
['Is variant of'] = {pagename},
}
for w,v in pairs(self._smwSubobject) do
local smwfunc = smw_to_func[w] or smw_to_func.default
local curarg = self.rargs[w]
if curarg then
local argval = curarg.d
if curarg.switches then
argval = curarg.switches[i]
if not Infobox.isDefined(argval) then
argval = curarg.d
end
end
if Infobox.isDefined(argval) then
local _x = mw.text.split(tostring(argval), Infobox.splitpoint, true)
for _, _x1 in ipairs(_x) do
_x1 = smwfunc(_x1)
if _x1 ~= 'unknown' then
if not subobj_data[v] then
subobj_data[v] = {}
end
table.insert(subobj_data[v], _x1)
end
end
end
end
end
for w,v in ipairs(k) do
local subobj_name = v
-- can't have a . in the first 5 characters of a subobject identifier
if mw.ustring.find(mw.ustring.sub(subobj_name,0,5), '%.') then
self:wikitext('[[Category:Pages with an invalid subobject name]]')
subobj_name = mw.ustring.gsub(subobj_name, '%.', '')
end
if subobj_name == '0' or subobj_name == '' then
self:wikitext('[[Category:Pages with an invalid subobject name]]')
subobj_name = 'v_'..subobj_name
end
smw_data_arr_subobj[subobj_name] = subobj_data
end
end
end
end
local res = mw.smw.set(smw_data_arr)
local res_subobj = true
for w,v in pairs(smw_data_arr_subobj) do
res_subobj = mw.smw.subobject(v, w)
if not res_subobj == true then
break
end
end
if not (res == true and res_subobj == true)  then
self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data')
if not res == true then
self.smw_error_tag:tag('div'):wikitext('Error setting SMW properties: '+res.error):done()
end
if not res_subobj == true then
self.smw_error_tag:tag('div'):wikitext('Error setting SMW subobject properties: '+res_subobj.error):done()
end
end
if self.setSMWElement then
if self.smw_error_tag == '' then
self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data')
end
end
for i,v in pairs(smw_data_arr_elem) do
lastheader = num
for j,k in ipairs(v) do
elseif args['data' .. tostring(num)] and
if k ~= 'unknown' then
args['data' .. tostring(num)]:gsub(
self.smw_error_tag:tag('span'):wikitext(mw.ustring.format('%s: [[%s::%s]]', i,i,k ))
category_in_empty_row_pattern, ''
end
):match('^%S') then
end
local data = args['data' .. tostring(num)]
if data:gsub(category_in_empty_row_pattern, ''):match('%S') then
lastheader = nil
end
end
end
end
if self._smwSubobjectName then
if self.smw_error_tag == '' then
self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data')
end
for w,v in pairs(smw_data_arr_subobj) do
local subobjdiv = self.smw_error_tag:tag('div')
subobjdiv:tag('span'):wikitext('SMW Subobject for '..w)
for j,k in pairs(v) do
subobjdiv:tag('span'):wikitext(mw.ustring.format('%s: %s', j, table.concat(k, ', ')))
end
end
end
end
-- Add view and talk links to infobox
-- Only done if a name is defined
if self.infoboxname and not self.bottomlinks.hide then
local bottom_links = {}
for _, v in ipairs(self.bottomlinks.links) do
table.insert(bottom_links,
string.format(
table.concat({'[[',
v[1],
'|',
v[2],
']]'}),
self.infoboxname,
self.infoboxname,
self.infoboxname,
self.infoboxname,
self.infoboxname)
)
end
bottom_links = table.concat(bottom_links,' &bull; ')
self.rtable:tag('tr'):tag('td')
:addClass('infobox-template-links')
:attr('colspan', self.bottomlinks.colspan)
:wikitext(bottom_links)
:done()
end
end
-- Define as finished
if lastheader then
self.__finished = true
args['header' .. tostring(lastheader)] = nil
end
 
--[[
Function for defining parameters
-- name : parameter name
-- func : function to define param, defaults to looking at blanks
DO NOT DEFINE VERSION HERE
USE :maxVersion()
Can be used any number of times for efficient definition
--]]
function Infobox:defineParams(...)
for _, v in ipairs(...) do
-- For every parameter, store its corresponding function to self.params
if v.name then
-- If the value is a function or a table (which should define a function)
if type(v.func) == 'function' or type(v.func) == 'table' then
self.params[v.name] = v.func
-- If the value is a string, use the predefined Infobox function of that name
elseif type(v.func) == 'string' then
self.params[v.name] = func_map[v.func] or hasContent
-- Everything else just looks for blanks
else
self.params[v.name] = hasContent()
end
-- Create a list of all param names
table.insert(self.paramnames,v.name)
-- function to allow duplicated values
if v.dupes then
self.dupeable[v.name] = true
end
end
end
end
end
end


--[[
-- Gets the union of the header and data argument numbers,
-- Forces an infobox to only use 1 variant
-- and renders them all in order
-- Mainly used by lite infoboxes
local function renderRows()
-- This should be run before creation
--]]
function Infobox:noSwitch()
self.versions = 1
self.switchfo = false
end


--[[
local rownums = union(getArgNums('header'), getArgNums('data'))
-- Calculates the max version
table.sort(rownums)
-- Adds labels
for k, num in ipairs(rownums) do
-- Sees if this needs to be a switch infobox
addRow({
-- Returns extra version count (even if already run)
header = args['header' .. tostring(num)],
--]]
label = args['label' .. tostring(num)],
function Infobox.maxVersion(box)
data = args['data' .. tostring(num)],
-- Only allowed to run once
datastyle = args.datastyle,
if box.versions ~= -1 then
class = args['class' .. tostring(num)],
return box.versions
rowclass = args['rowclass' .. tostring(num)],
-- @deprecated next; target .infobox-<name> rowclass
rowstyle = args['rowstyle' .. tostring(num)],
rowcellstyle = args['rowcellstyle' .. tostring(num)]
})
end
end
end


box.labels = {}
local function renderNavBar()
box.versions = 0
if not args.name then return end
local smwname = {}
if string.lower(box.args['smw'] or '') == 'no' then
box.suppressAllSMW = true
end
-- Look for up to 125 variants, defined in order
for i=1, 125 do
-- If variant# exists
if box.args['version'..i] then
-- Increase version count
box.versions = box.versions + 1


-- Add its label
has_rows = true
local label = box.args['version'..i] or ('Version '..i)
root
table.insert(box.labels,label)
:tag('tr')
:tag('td')
-- add to smwname
:attr('colspan', '2')
if box.args['smwname'..i] then
:addClass('infobox-navbar')
table.insert(smwname, mw.text.split(box.args['smwname'..i], '¦'))
:wikitext(require('Module:Navbar')._navbar{
else
args.name,
table.insert(smwname, {label})
mini = 1,
end
})
end
box.suppressVersionSMW[i] = (box.args['smw'..i] and string.lower(box.args['smw'..i] or '') == 'no')
else
-- Stop if it doesn't exist
break
end
end


-- Define self as a switch infobox if at least 1 other version is found
local function renderItalicTitle()
if box.versions > 0 then
local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title'])
box.switchfo = true
if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then
box._smwSubobjectName = smwname
root:wikitext(require('Module:Italic title')._main({}))
else
-- single version, check for smwname
if box.args['smwname'] then
box._smwSubobjectName = {mw.text.split(box.args['smwname'], '¦')}
end
end
end
-- versions calculated
return box.versions
end
end


--[[
-- Categories in otherwise empty rows are collected in empty_row_categories.
-- Cleans parameters as defined by the above function
-- This function adds them to the module output. It is not affected by
-- SHOULD NOT BE RUN UNTIL ALL THINGS ARE DEFINED
-- args.decat because this module should not prevent module-external categories
-- Handles switches as well
-- from rendering.
-- adds table _add to rargs, a cleaned up version of arguments
local function renderEmptyRowCategories()
-- d : default value
for _, s in ipairs(empty_row_categories) do
-- switches : table of switches (in numerical order)
root:wikitext(s)
-- Functions can be defined with tables
---- name : name of function
---- params : table of args to pass to functions
---- flag : flags for input
d | #default : use the cleaned parameter first, otherwise passed
p : use the passed value of parameters
r | l : use raw (literal) text, rather than values
-- Defining a single flag will use that flag on all parameters
-- Defining a table of flags will use the respective flag by position
--]]
function Infobox:cleanParams()
-- map of flags to functionality
local flagmap = {
r = 'r',
l = 'r',
d = 'd',
p = 'p'
}
-- For all parameters named
for _, v in ipairs(self.paramnames) do
-- Parameter to add
local _add = {}
local catdata = { all_defined = true, one_defined = false }
-- If the value of params is a function
if type(self.params[v]) == 'function' then
-- Perform that function with the parameter
_add.d = self.params[v](self.args[v])
-- If it's a table, parse it into a function
elseif type(self.params[v]) == 'table' then
-- Find the functions name
local func = self.params[v].name
 
if type(func) == 'string' then
func = func_map[func]
end
 
-- catch all
if type(func) ~= 'function' then
func = has_content
end
 
-- Recreate table of args and flags
local func_args = {}
local flag = {}
-- If the flags are NOT a table, turn them into a table
-- Same size as the parameter table
-- Every flag will be the same
if type(self.params[v].flag) ~= 'table' then
-- Map flags, if unmapped, use default
local _flag = flagmap[self.params[v].flag] or 'd'
-- recreate table
for x=1,#self.params[v].params do
table.insert(flag,_flag)
end
-- If flags are already a table, recreate them in new table
elseif type(self.params[v].flag) == 'table' then
local _flag = self.params[v].flag
-- recreate table
for x=1,#self.params[v].params do
-- Map flags, if unmapped, use default
table.insert(flag,flagmap[_flag[x]] or 'd')
end
end
-- Recreate param table, parsing flags as instructions
for x, w in ipairs(self.params[v].params) do
local xarg
-- By default or defined as 'd'
-- looks for the cleaned value of the named parameter first
-- if it doesn't exist, look at the passed value next
-- if that doesn't exist, use blank
if flag[x] == 'd' then
xarg = self.rargs[w] and self.rargs[w].d
-- compare to nil explicitly because false is a valid value
if xarg == nil then
xarg = self.args[w] or ''
end
-- Look only at the passed value of the named parameter
-- if that doesn't exist, use blank
elseif flag[x] == 'p' then
xarg = self.args[w] or ''
-- Don't interpret value as a parameter name, and paste the as is
elseif flag[x] == 'r' then
xarg = w
end
-- Add parsed argument to table
table.insert(func_args,xarg)
end
-- Run function
_add.d = func(unpack(func_args))
end
 
if _add.d == nil or _add.d == nil_param then
-- have to do pagename defaults here to prevent weird behaviour with switch values
if v == 'name' then
_add.d = pagename
else
_add.d = edit
end
catdata.all_defined = false
else
--_add.d is not nil
catdata.one_defined = true
end
if self.switchfo then
-- Table of switches values and count of them
local _add_switch = {}
local switches = 0
-- Look for up to the maximum number
for i=1, self.versions do
local _addarg
-- see if this param is allowed to have switch
if v ~= 'image' and v ~= 'examine' and string.find(self.args[v..i] or '','%$%d') then
local refi = string.match(self.args[v..i],'%$(%d+)')
_addarg = _add_switch[tonumber(refi)] or nil_param
else
-- If the value of params is a function
if type(self.params[v]) == 'function' then
-- Perform that function with the parameter at that index
_addarg = self.params[v](self.args[v..i])
-- If it's a table, parse it into a function
elseif type(self.params[v]) == 'table' then
-- Find the functions name
local func = self.params[v].name
 
 
if type(func) == 'string' then
func = func_map[func]
end
 
-- catch all
if type(func) ~= 'function' then
func = has_content
end
 
-- Recreate table of args and flags
local func_args = {}
local flag = {}
-- If the flags are NOT a table, turn them into a table
-- Same size as the parameter table
-- Every flag will be the same
if type(self.params[v].flag) ~= 'table' then
-- Map flags, if unmapped, use default
local _flag = flagmap[self.params[v].flag] or 'd'
-- recreate table
for x=1,#self.params[v].params do
table.insert(flag,_flag)
end
-- If flags are already a table, recreate them in new table
elseif type(self.params[v].flag) == 'table' then
local _flag = self.params[v].flag
-- recreate table
for x=1,#self.params[v].params do
-- Map flags, if unmapped, use default
table.insert(flag,flagmap[_flag[x]] or 'd')
end
end
-- Recreate param table, parsing flags as instructions
for x, w in ipairs(self.params[v].params) do
local xarg
-- By default or defined as 'd'
-- looks for the cleaned value of the named parameter first
-- if it doesn't exist, look at the passed value next
-- if that doesn't exist, look at the default
-- if that doesn't exist, use blank
if flag[x] == 'd' then
if self.rargs[w] then
if self.rargs[w].switches then
xarg = self.rargs[w].switches[i]
else
xarg = self.args[w..i]
end
if xarg == nil or xarg == nil_param then
xarg = self.rargs[w].d
end
end
-- multiple catches in a row just to cover everything
if xarg == nil or xarg == nil_param then
xarg = self.args[w..i]
end
if xarg == nil or xarg == edit or xarg == nil_param then
xarg = self.args[w]
end
if xarg == nil or xarg == edit or xarg == nil_param then
xarg = ''
end
-- Look only at the passed value of the named parameter
-- if that doesn't exist, use unnumbered parameter
-- if that doesn't exist, use blank
elseif flag[x] == 'p' then
xarg = self.args[w..i] or self.args[w] or ''
-- Don't interpret value as a parameter name, and paste the as is
elseif flag[x] == 'r' then
xarg = w
end
-- Add parsed argument to table
table.insert(func_args,xarg)
end
-- Run function
_addarg = func(unpack(func_args))
end
end
-- If not defined, add the nil_param value
-- An actual nil would cause errors in placement
-- So it needs to be filled with an actual value
-- "nil_param" is understood as nil in other functions
-- Include table in case parameter isn't defined by template
 
if _addarg == nil or _addarg == nil_param then
table.insert(_add_switch, nil_param)
else
switches = switches + 1
table.insert(_add_switch, _addarg)
catdata.one_defined = true
end
end
-- If there are actually other values to switch to
-- Define a switches subtable, otherwise ignore it
if switches > 0 then
_add.switches = _add_switch
end
end
 
-- Quick fix for names (which defaults to pagename)
if v == 'name' then
if _add.d == pagename then
if _add.switches and _add.switches[1] ~= pagename and _add.switches[1] ~= nil_param then
_add.d = _add.switches[1]
end
end
end
 
-- Parameter cleaning finished, add to table of cleaned args
self.rargs[v] = _add
 
-- Category metadata
-- If every param except default is defined, all_defined = true
if catdata.all_defined == false then
if _add.d == edit then
if _add.switches then
catdata.all_defined = true
for _, v in ipairs(_add.switches) do
if v == nil_param then
catdata.all_defined = false
break
end
end
end
end
end
 
self.catdata[v] = catdata
end
end
end


-- mass dupe removal
-- Render tracking categories. args.decat == turns off tracking categories.
-- this needs to be done at the end to keep dependent parameters working
local function renderTrackingCategories()
-- also removes incompatible data types
if args.decat == 'yes' then return end
for _, v in ipairs(self.paramnames) do
if args.child == 'yes' then
-- not removed from dupe enabled params parameters
if args.title then
if not self.dupeable[v] and self.rargs[v] and self.rargs[v].switches then
root:wikitext(
-- tells us whether or not we'll need to remove the switch data
'[[Category:Pages using embedded infobox templates with the title parameter]]'
-- switched to false if a switch values does not match the default
)
local rmvswitch = true
for q, z in ipairs(self.rargs[v].switches) do
-- remove types that don't turn into strings properly
if type(z) == 'table' or type(z) == 'function' then
self.rargs[v].switches[q] = nil_param
 
-- if it isn't nil or an edit button
-- change variable to keep the switch data
elseif z ~= nil_param and z ~= edit then
rmvswitch = false
end
end
-- remove switch data if everything was a dupe
if rmvswitch then
self.rargs[v].switches = nil
end
end
end
elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then
root:wikitext('[[Category:Articles using infobox templates with no data rows]]')
end
end
end


-- Title parentheses (has to be done here, sadly)
--[=[
local _name
Loads the templatestyles for the infobox.
if self.rargs.name then
_name = self.rargs.name.d
-- replace html entities to their actual character
_name = mw.text.decode(_name)


-- if the page name matches the item name, don't add parentheses
TODO: FINISH loading base templatestyles here rather than in
if _name == mw.title.getCurrentTitle().fullText then
MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables.
self.rtable:addClass('no-parenthesis-style')
See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :).
end
When we do this we should clean up the inline CSS below too.
end
Will have to do some bizarre conversion category like with sidebar.
end


--[[
]=]
Function to link internal use parameters with JS class attribution
local function loadTemplateStyles()
-- self:linkParams{ { arg1, arg2 }, ... { arg1a, arg2a } }
local frame = mw.getCurrentFrame()
-- arg1: parameter name being linked
-- arg2: parameter name that holds the classes
local hlist_templatestyles = ''
-- THIS FUNCTION SHOULD BE RUN AFTER :cleanParams()
if lists.hlist_t.found then
-- THIS FUNCTION SHOULD BE RUN BEFORE :finish()
hlist_templatestyles = frame:extensionTag{
-- The second argument's data should always contain a value (a CSS class name) at every index
name = 'templatestyles', args = { src = lists.hlist_t.styles }
-- This function is cancelled for non switch boxes
}
--]]
function Infobox:linkParams(...)
if not self.switchfo then
return
end
end
for _, v in ipairs(...) do
self.switchfoattr[v[1]] = self.rargs[v[2]]
local plainlist_templatestyles = ''
if lists.plainlist_t.found then
plainlist_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = lists.plainlist_t.styles }
}
end
end
end
 
-- See function description
--[==========================================[
local base_templatestyles = frame:extensionTag{
-- Functions for accessing parameters easily
name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' }
--]==========================================]
--[[
Access the param
-- arg : param name
-- retp : return type
d | #default : self.rargs[arg].d -- Default value
f | full : self.rargs[arg] -- Entire table
s | switches : self.rargs[arg].switches -- Entire switch table
s# : self.rargs[arg].switches[#] -- Single switch value at index #
r : switches[1] or d
--]]
function Infobox:param(arg, retp)
-- All parameters
if arg == 'all' then
return self.rargs
end
-- case-insensitive flagging
retp = tostring(retp):lower()
local fmap = {
d = 'd', default = 'd', s0 = 'd', -- let s-naught count as default (since that's what it is)
f = 'f', full = 'f',
s = 's', switch = 's', switches = 's',
r = 'r'
}
}
local ret_func
-- quickly see if the parameter is a value greater than 0
if retp:match('s[1-9]') then
ret_func = 's2'
else
-- Otherwise map it to the correct flag, or the default
ret_func = fmap[retp] or fmap.d
end


-- Fetch parameter
local templatestyles = ''
local param = self.rargs[arg]
if args['templatestyles'] then
-- Return nil if no table found
templatestyles = frame:extensionTag{
if not param then return nil end
name = 'templatestyles', args = { src = args['templatestyles'] }
 
}
-- Return default
if ret_func == 'd' then
return param.d
end
end
 
-- Return full table
local child_templatestyles = ''
if ret_func == 'f' then
if args['child templatestyles'] then
return param
child_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['child templatestyles'] }
}
end
end
 
-- Return switch table
local grandchild_templatestyles = ''
if ret_func == 's' then
if args['grandchild templatestyles'] then
return param.switches
grandchild_templatestyles = frame:extensionTag{
end
name = 'templatestyles', args = { src = args['grandchild templatestyles'] }
 
}
-- Return the first switch, otherwise the default
if ret_func == 'r' then
if not param.switches then
return param.d
elseif param.switches[1] == nil_param then
return param.d
else
return param.switches[1]
end
end
 
-- If s2, reread the param
if ret_func == 's2' then
-- no switches
if not param.switches then
return nil
end
-- Parse index by removing the s
local index = retp:match('s(%d+)')
-- nil_param
if param.switches[index] == nil_param then
return nil
else
return param.switches[index]
end
end
end
return table.concat({
-- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering.
-- this ordering is not a guarantee because the rows of interest invoking
-- each class may not be on a specific page
hlist_templatestyles,
plainlist_templatestyles,
base_templatestyles,
templatestyles,
child_templatestyles,
grandchild_templatestyles
})
end
end


--[[
-- common functions between the child and non child cases
Checks if a parameter is defined and not blank
local function structure_infobox_common()
-- arg : parameter to look at
renderSubheaders()
-- index : index of switches to look at (defaults to default param)
renderImages()
-- defining 'all' will look at every possible value for the parameter
preprocessRows()
--]]
renderRows()
function Infobox:paramDefined(arg,index)
renderBelowRow()
-- Can use cleaned params for switches
renderNavBar()
-- but need the passed to identify blanks in the template
renderItalicTitle()
local param = self.rargs[arg]
renderEmptyRowCategories()
local _arg = self.args[arg]
renderTrackingCategories()
if string.find(_arg or '','%?action=edit') then
cleanInfobox()
_arg = ''
end
index = index or 0
local ret
-- create a long strong of every value to test for things if 'all'
if string.lower(index) == 'all' then
return self.catdata[arg] and (self.catdata[arg].one_defined or self.catdata[arg].all_defined)
-- index to number otherwise
else
index = tonumber(index) or 0
if index == 0 then
if param.switches then
if Infobox.isDefined(param.switches[1]) then
ret = param.switches[1]
else
ret = _arg
end
else
ret = _arg
end
else
if not param.switches then
return nil
end
if param.switches[index] == nil_param then
return nil
end
ret = param.switches[index]
end
end
return tostring(ret or ''):find('%S')
end
end


--[[
-- Specify the overall layout of the infobox, with special settings if the
Function to perform a search on all parameters of a defined name
-- infobox is used as a 'child' inside another infobox.
-- param: param name
local function _infobox()
-- val: a value or function
if args.child ~= 'yes' then
-- functions passed must return either true or false
root = mw.html.create('table')
-- with true being counted as a match
--]]
function Infobox:paramGrep(param,val)
local arg = self.rargs[param]
-- if no parameters, return nil
if not arg then
return nil
end


local ret
root
local valtype = type(val)
:addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox')
-- start with the default value
:addClass(args.bodyclass)
-- if it's a function, run it
-- @deprecated next; target .infobox-<name>
if valtype == 'function' then
:cssText(args.bodystyle)
ret = val(arg.d)
has_list_class({ args.bodyclass })


-- true means it matched
renderTitle()
if ret == true then
renderAboveRow()
return ret
else
end
root = mw.html.create()
 
-- switches up here for functions
if arg.switches then
for _, v in ipairs(arg.switches) do
ret = val(v)
if ret == true then
return true
end
end
end


-- if it's just a value, compare the two
root
-- only run if types match to avoid errors
:wikitext(args.title)
-- compare switches later
elseif valtype == type(arg.d) then
-- if a string, make case insensitive
if valtype == 'string' then
if string.lower(val) == string.lower(arg.d) then
return true
end
-- everything else just test directly
elseif val == arg.d then
return true
end
end
end
structure_infobox_common()
return loadTemplateStyles() .. root
end


-- switch cases
-- If the argument exists and isn't blank, add it to the argument table.
-- more organised putting them here
-- Blank arguments are treated as nil to match the behaviour of ParserFunctions.
if arg.switches then
local function preprocessSingleArg(argName)
for _, v in ipairs(arg.switches) do
if origArgs[argName] and origArgs[argName] ~= '' then
if valtype == type(v) then
args[argName] = origArgs[argName]
if valtype == 'string' then
if val:lower() == v:lower() then
return true
end
elseif val == v then
return true
end
end
end
end
end
-- return false in every other case
return false
end
end
------


function Infobox.paramRead(arg,val)
-- Assign the parameters with the given prefixes to the args table, in order, in
-- if no parameters, return nil
-- batches of the step size specified. This is to prevent references etc. from
if not arg then
-- appearing in the wrong order. The prefixTable should be an array containing
return nil
-- tables, each of which has two possible fields, a "prefix" string and a
-- "depend" table. The function always parses parameters containing the "prefix"
-- string, but only parses parameters in the "depend" table if the prefix
-- parameter is present and non-blank.
local function preprocessArgs(prefixTable, step)
if type(prefixTable) ~= 'table' then
error("Non-table value detected for the prefix table", 2)
end
if type(step) ~= 'number' then
error("Invalid step value detected", 2)
end
end


local ret
-- Get arguments without a number suffix, and check for bad input.
local valtype = type(val)
for i,v in ipairs(prefixTable) do
-- start with the default value
if type(v) ~= 'table' or type(v.prefix) ~= "string" or
-- if it's a function, run it
(v.depend and type(v.depend) ~= 'table') then
if valtype == 'function' then
error('Invalid input detected to preprocessArgs prefix table', 2)
ret = val(arg.d)
 
-- true means it matched
if ret == true then
return ret
end
end
 
preprocessSingleArg(v.prefix)
-- switches up here for functions
-- Only parse the depend parameter if the prefix parameter is present
if arg.switches then
-- and not blank.
for _, v in ipairs(arg.switches) do
if args[v.prefix] and v.depend then
ret = val(v)
for j, dependValue in ipairs(v.depend) do
if ret == true then
if type(dependValue) ~= 'string' then
return true
error('Invalid "depend" parameter value detected in preprocessArgs')
end
end
preprocessSingleArg(dependValue)
end
end
end
-- if it's just a value, compare the two
-- only run if types match to avoid errors
-- compare switches later
elseif valtype == type(arg.d) then
-- if a string, make case insensitive
if valtype == 'string' then
if string.lower(val) == string.lower(arg.d) then
return true
end
-- everything else just test directly
elseif val == arg.d then
return true
end
end
end
end


-- switch cases
-- Get arguments with number suffixes.
-- more organised putting them here
local a = 1 -- Counter variable.
if arg.switches then
local moreArgumentsExist = true
for _, v in ipairs(arg.switches) do
while moreArgumentsExist == true do
if valtype == type(v) then
moreArgumentsExist = false
if valtype == 'string' then
for i = a, a + step - 1 do
if val:lower() == v:lower() then
for j,v in ipairs(prefixTable) do
return true
local prefixArgName = v.prefix .. tostring(i)
if origArgs[prefixArgName] then
-- Do another loop if any arguments are found, even blank ones.
moreArgumentsExist = true
preprocessSingleArg(prefixArgName)
end
-- Process the depend table if the prefix argument is present
-- and not blank, or we are processing "prefix1" and "prefix" is
-- present and not blank, and if the depend table is present.
if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then
for j,dependValue in ipairs(v.depend) do
local dependArgName = dependValue .. tostring(i)
preprocessSingleArg(dependArgName)
end
end
elseif val == v then
return true
end
end
end
end
end
end
a = a + step
end
end
-- return false in every other case
return false
end
end


----
-- Parse the data parameters in the same order that the old {{infobox}} did, so
-- that references etc. will display in the expected places. Parameters that
-- depend on another parameter are only processed if that parameter is present,
-- to avoid phantom references appearing in article reference lists.
local function parseDataParameters()


-- Return collected category data
preprocessSingleArg('autoheaders')
function Infobox:categoryData()
preprocessSingleArg('child')
return self.catdata
preprocessSingleArg('bodyclass')
preprocessSingleArg('subbox')
preprocessSingleArg('bodystyle')
preprocessSingleArg('title')
preprocessSingleArg('titleclass')
preprocessSingleArg('titlestyle')
preprocessSingleArg('above')
preprocessSingleArg('aboveclass')
preprocessSingleArg('abovestyle')
preprocessArgs({
{prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}}
}, 10)
preprocessSingleArg('subheaderstyle')
preprocessSingleArg('subheaderclass')
preprocessArgs({
{prefix = 'image', depend = {'caption', 'imagerowclass'}}
}, 10)
preprocessSingleArg('captionstyle')
preprocessSingleArg('imagestyle')
preprocessSingleArg('imageclass')
preprocessArgs({
{prefix = 'header'},
{prefix = 'data', depend = {'label'}},
{prefix = 'rowclass'},
{prefix = 'rowstyle'},
{prefix = 'rowcellstyle'},
{prefix = 'class'}
}, 50)
preprocessSingleArg('headerclass')
preprocessSingleArg('headerstyle')
preprocessSingleArg('labelstyle')
preprocessSingleArg('datastyle')
preprocessSingleArg('below')
preprocessSingleArg('belowclass')
preprocessSingleArg('belowstyle')
preprocessSingleArg('name')
-- different behaviour for italics if blank or absent
args['italic title'] = origArgs['italic title']
preprocessSingleArg('decat')
preprocessSingleArg('templatestyles')
preprocessSingleArg('child templatestyles')
preprocessSingleArg('grandchild templatestyles')
end
end


-- Infobox:addDropLevelVars("thieving", "skilllvl1")
-- If called via #invoke, use the args passed into the invoking template.
function Infobox:addDropLevelVars(key, paramName)
-- Otherwise, for testing purposes, assume args are being passed directly in.
local levelParams = self:param(paramName, 'f')
function p.infobox(frame)
local dropParams = self:param('dropversion', 'f')
if frame == mw.getCurrentFrame() then
if levelParams == nil then
origArgs = frame:getParent().args
return
else
end
origArgs = frame
if dropParams == nil or not self:paramDefined("dropversion", "all") then
dropParams = {d = 'DEFAULT'}
end
if dropParams.switches == nil then
dropParams.switches = {}
end
local levels = levelParams.switches or {levelParams.d}
local dropVersions = {}
for i=1,#levels do
local dropVersionFromInfobox = dropParams.switches[i] or dropParams.d
if dropVersionFromInfobox == nil_param then
dropVersionFromInfobox = 'DEFAULT'
end
for _, dropVersion in ipairs(mw.text.split(dropVersionFromInfobox, ',')) do
if dropVersions[dropVersion] == nil then
dropVersions[dropVersion] = {}
end
dropVersions[dropVersion][levels[i]] = true
end
end
-- This part is to append levels from previous Infobox invocations
for dropVersion, dropLevels in pairs(dropVersions) do
-- set dummy property on versioned SMW subobject, otherwise it can't be part of an #ask
mw.smw.subobject({["Dummy property"] = true}, dropVersion)
-- example variable: DropLevel_combat_High_level
local var_name = string.format("DropLevel_%s_%s", key, dropVersion)
local previousVar = var.var(var_name)
if previousVar ~= "" then
for i, v in ipairs(mw.text.split(previousVar, ',')) do
dropVersions[dropVersion][v] = true
end
end
local ordered = {}
for k, v in pairs(dropVersions[dropVersion]) do
local n = tonumber(k)
if n ~= nil then
table.insert(ordered, n)
end
end
table.sort(ordered)
var.vardefine(var_name, table.concat(ordered, ','))
end
end
parseDataParameters()
return _infobox()
end
end


-- Override tostring
-- For calling via #invoke within a template
function Infobox.tostring(box)
function p.infoboxTemplate(frame)
-- If not finished, finish
origArgs = {}
if not box.__finished then
for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end
box:finish()
end
parseDataParameters()
 
-- Make entire html wrapper a string and return it
return _infobox()
local btns = box.switch_buttons_tag
if box.custom_buttons then
btns = ''
end
if box.args.__dump__ then
return '<' .. 'pre>'..mw.dumpObject(box) .. '</' .. 'pre>[[Category:Dumping infoboxes]]'
end
return tostring(btns) .. tostring(box.rtable) .. table.concat(box.appendStrs, '') .. tostring(box.switch_tag) .. tostring(box.smw_error_tag)
end
end
 
return p
return Infobox
-- </nowiki>

Revision as of 16:29, 27 December 2022

Lua error in Module:TNT at line 159: Missing JsonConfig extension; Cannot load https://commons.wikimedia.org/wiki/Data:I18n/Uses TemplateStyles.tab.

Module:Infobox is a module that implements the {{Infobox}} template. Please see the template page for usage instructions.

Tracking categories


local p = {}
local args = {}
local origArgs = {}
local root
local empty_row_categories = {}
local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]'
local has_rows = false
local lists = {
	plainlist_t = {
		patterns = {
			'^plainlist$',
			'%splainlist$',
			'^plainlist%s',
			'%splainlist%s'
		},
		found = false,
		styles = 'Plainlist/styles.css'
	},
	hlist_t = {
		patterns = {
			'^hlist$',
			'%shlist$',
			'^hlist%s',
			'%shlist%s'
		},
		found = false,
		styles = 'Hlist/styles.css'
	}
}

local function has_list_class(args_to_check)
	for _, list in pairs(lists) do
		if not list.found then
			for _, arg in pairs(args_to_check) do
				for _, pattern in ipairs(list.patterns) do
					if mw.ustring.find(arg or '', pattern) then
						list.found = true
						break
					end
				end
				if list.found then break end
			end
		end
	end
end

local function fixChildBoxes(sval, tt)
	local function notempty( s ) return s and s:match( '%S' ) end
	
	if notempty(sval) then
		local marker = '<span class=special_infobox_marker>'
		local s = sval
		-- start moving templatestyles and categories inside of table rows
		local slast = ''
		while slast ~= s do
			slast = s
			s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1')
			s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1')
		end
		-- end moving templatestyles and categories inside of table rows
		s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1')
		s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker)
		if s:match(marker) then
			s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '')
			s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1')
			s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1')
			s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1')
			s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1')
		end
		if s:match(marker) then
			local subcells = mw.text.split(s, marker)
			s = ''
			for k = 1, #subcells do
				if k == 1 then
					s = s .. subcells[k] .. '</' .. tt .. '></tr>'
				elseif k == #subcells then
					local rowstyle = ' style="display:none"'
					if notempty(subcells[k]) then rowstyle = ''	end
					s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' ..
						subcells[k]
				elseif notempty(subcells[k]) then
					if (k % 2) == 0 then
						s = s .. subcells[k]
					else
						s = s .. '<tr><' .. tt .. ' colspan=2>\n' ..
							subcells[k] .. '</' .. tt .. '></tr>'
					end
				end
			end
		end
		-- the next two lines add a newline at the end of lists for the PHP parser
		-- [[Special:Diff/849054481]]
		-- remove when [[:phab:T191516]] is fixed or OBE
		s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n')
		s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n')
		s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1')
		s = mw.ustring.gsub(s, '^(%{%|)', '\n%1')
		return s
	else
		return sval
	end
end

-- Cleans empty tables
local function cleanInfobox()
	root = tostring(root)
	if has_rows == false then
		root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '')
	end
end

-- Returns the union of the values of two tables, as a sequence.
local function union(t1, t2)

	local vals = {}
	for k, v in pairs(t1) do
		vals[v] = true
	end
	for k, v in pairs(t2) do
		vals[v] = true
	end
	local ret = {}
	for k, v in pairs(vals) do
		table.insert(ret, k)
	end
	return ret
end

-- Returns a table containing the numbers of the arguments that exist
-- for the specified prefix. For example, if the prefix was 'data', and
-- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}.
local function getArgNums(prefix)
	local nums = {}
	for k, v in pairs(args) do
		local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$')
		if num then table.insert(nums, tonumber(num)) end
	end
	table.sort(nums)
	return nums
end

-- Adds a row to the infobox, with either a header cell
-- or a label/data cell combination.
local function addRow(rowArgs)
	
	if rowArgs.header and rowArgs.header ~= '_BLANK_' then
		has_rows = true
		has_list_class({ rowArgs.rowclass, rowArgs.class, args.headerclass })
		
		root
			:tag('tr')
				:addClass(rowArgs.rowclass)
				:cssText(rowArgs.rowstyle)
				:tag('th')
					:attr('colspan', '2')
					:addClass('infobox-header')
					:addClass(rowArgs.class)
					:addClass(args.headerclass)
					-- @deprecated next; target .infobox-<name> .infobox-header
					:cssText(args.headerstyle)
					:cssText(rowArgs.rowcellstyle)
					:wikitext(fixChildBoxes(rowArgs.header, 'th'))
		if rowArgs.data then
			root:wikitext(
				'[[Category:Pages using infobox templates with ignored data cells]]'
			)
		end
	elseif rowArgs.data and rowArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
		has_rows = true
		has_list_class({ rowArgs.rowclass, rowArgs.class })
		
		local row = root:tag('tr')
		row:addClass(rowArgs.rowclass)
		row:cssText(rowArgs.rowstyle)
		if rowArgs.label then
			row
				:tag('th')
					:attr('scope', 'row')
					:addClass('infobox-label')
					-- @deprecated next; target .infobox-<name> .infobox-label
					:cssText(args.labelstyle)
					:cssText(rowArgs.rowcellstyle)
					:wikitext(rowArgs.label)
					:done()
		end

		local dataCell = row:tag('td')
		dataCell
			:attr('colspan', not rowArgs.label and '2' or nil)
			:addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data')
			:addClass(rowArgs.class)
			-- @deprecated next; target .infobox-<name> .infobox(-full)-data
			:cssText(rowArgs.datastyle)
			:cssText(rowArgs.rowcellstyle)
			:wikitext(fixChildBoxes(rowArgs.data, 'td'))
	else
		table.insert(empty_row_categories, rowArgs.data or '')
	end
end

local function renderTitle()
	if not args.title then return end

	has_rows = true
	has_list_class({args.titleclass})
	
	root
		:tag('caption')
			:addClass('infobox-title')
			:addClass(args.titleclass)
			-- @deprecated next; target .infobox-<name> .infobox-title
			:cssText(args.titlestyle)
			:wikitext(args.title)
	
end

local function renderAboveRow()
	if not args.above then return end

	has_rows = true
	has_list_class({ args.aboveclass })
	
	root
		:tag('tr')
			:tag('th')
				:attr('colspan', '2')
				:addClass('infobox-above')
				:addClass(args.aboveclass)
				-- @deprecated next; target .infobox-<name> .infobox-above
				:cssText(args.abovestyle)
				:wikitext(fixChildBoxes(args.above,'th'))
end

local function renderBelowRow()
	if not args.below then return end

	has_rows = true
	has_list_class({ args.belowclass })
	
	root
		:tag('tr')
			:tag('td')
				:attr('colspan', '2')
				:addClass('infobox-below')
				:addClass(args.belowclass)
				-- @deprecated next; target .infobox-<name> .infobox-below
				:cssText(args.belowstyle)
				:wikitext(fixChildBoxes(args.below,'td'))
end

local function addSubheaderRow(subheaderArgs)
	if subheaderArgs.data and
		subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
		has_rows = true
		has_list_class({ subheaderArgs.rowclass, subheaderArgs.class })
		
		local row = root:tag('tr')
		row:addClass(subheaderArgs.rowclass)

		local dataCell = row:tag('td')
		dataCell
			:attr('colspan', '2')
			:addClass('infobox-subheader')
			:addClass(subheaderArgs.class)
			:cssText(subheaderArgs.datastyle)
			:cssText(subheaderArgs.rowcellstyle)
			:wikitext(fixChildBoxes(subheaderArgs.data, 'td'))
	else
		table.insert(empty_row_categories, subheaderArgs.data or '')
	end
end

local function renderSubheaders()
	if args.subheader then
		args.subheader1 = args.subheader
	end
	if args.subheaderrowclass then
		args.subheaderrowclass1 = args.subheaderrowclass
	end
	local subheadernums = getArgNums('subheader')
	for k, num in ipairs(subheadernums) do
		addSubheaderRow({
			data = args['subheader' .. tostring(num)],
			-- @deprecated next; target .infobox-<name> .infobox-subheader
			datastyle = args.subheaderstyle,
			rowcellstyle = args['subheaderstyle' .. tostring(num)],
			class = args.subheaderclass,
			rowclass = args['subheaderrowclass' .. tostring(num)]
		})
	end
end

local function addImageRow(imageArgs)

	if imageArgs.data and
		imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then

		has_rows = true
		has_list_class({ imageArgs.rowclass, imageArgs.class })
		
		local row = root:tag('tr')
		row:addClass(imageArgs.rowclass)

		local dataCell = row:tag('td')
		dataCell
			:attr('colspan', '2')
			:addClass('infobox-image')
			:addClass(imageArgs.class)
			:cssText(imageArgs.datastyle)
			:wikitext(fixChildBoxes(imageArgs.data, 'td'))
	else
		table.insert(empty_row_categories, imageArgs.data or '')
	end
end

local function renderImages()
	if args.image then
		args.image1 = args.image
	end
	if args.caption then
		args.caption1 = args.caption
	end
	local imagenums = getArgNums('image')
	for k, num in ipairs(imagenums) do
		local caption = args['caption' .. tostring(num)]
		local data = mw.html.create():wikitext(args['image' .. tostring(num)])
		if caption then
			data
				:tag('div')
					:addClass('infobox-caption')
					-- @deprecated next; target .infobox-<name> .infobox-caption
					:cssText(args.captionstyle)
					:wikitext(caption)
		end
		addImageRow({
			data = tostring(data),
			-- @deprecated next; target .infobox-<name> .infobox-image
			datastyle = args.imagestyle,
			class = args.imageclass,
			rowclass = args['imagerowclass' .. tostring(num)]
		})
	end
end

-- When autoheaders are turned on, preprocesses the rows
local function preprocessRows()
	if not args.autoheaders then return end
	
	local rownums = union(getArgNums('header'), getArgNums('data'))
	table.sort(rownums)
	local lastheader
	for k, num in ipairs(rownums) do
		if args['header' .. tostring(num)] then
			if lastheader then
				args['header' .. tostring(lastheader)] = nil
			end
			lastheader = num
		elseif args['data' .. tostring(num)] and
			args['data' .. tostring(num)]:gsub(
				category_in_empty_row_pattern, ''
			):match('^%S') then
			local data = args['data' .. tostring(num)]
			if data:gsub(category_in_empty_row_pattern, ''):match('%S') then
				lastheader = nil
			end
		end
	end
	if lastheader then
		args['header' .. tostring(lastheader)] = nil
	end
end

-- Gets the union of the header and data argument numbers,
-- and renders them all in order
local function renderRows()

	local rownums = union(getArgNums('header'), getArgNums('data'))
	table.sort(rownums)
	for k, num in ipairs(rownums) do
		addRow({
			header = args['header' .. tostring(num)],
			label = args['label' .. tostring(num)],
			data = args['data' .. tostring(num)],
			datastyle = args.datastyle,
			class = args['class' .. tostring(num)],
			rowclass = args['rowclass' .. tostring(num)],
			-- @deprecated next; target .infobox-<name> rowclass
			rowstyle = args['rowstyle' .. tostring(num)],
			rowcellstyle = args['rowcellstyle' .. tostring(num)]
		})
	end
end

local function renderNavBar()
	if not args.name then return end

	has_rows = true
	root
		:tag('tr')
			:tag('td')
				:attr('colspan', '2')
				:addClass('infobox-navbar')
				:wikitext(require('Module:Navbar')._navbar{
					args.name,
					mini = 1,
				})
end

local function renderItalicTitle()
	local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title'])
	if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then
		root:wikitext(require('Module:Italic title')._main({}))
	end
end

-- Categories in otherwise empty rows are collected in empty_row_categories.
-- This function adds them to the module output. It is not affected by
-- args.decat because this module should not prevent module-external categories
-- from rendering.
local function renderEmptyRowCategories()
	for _, s in ipairs(empty_row_categories) do
		root:wikitext(s)
	end
end

-- Render tracking categories. args.decat == turns off tracking categories.
local function renderTrackingCategories()
	if args.decat == 'yes' then return end
	if args.child == 'yes' then
		if args.title then
			root:wikitext(
				'[[Category:Pages using embedded infobox templates with the title parameter]]'
			)
		end
	elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then
		root:wikitext('[[Category:Articles using infobox templates with no data rows]]')
	end
end

--[=[
Loads the templatestyles for the infobox.

TODO: FINISH loading base templatestyles here rather than in
MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables.
See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :).
When we do this we should clean up the inline CSS below too.
Will have to do some bizarre conversion category like with sidebar.

]=]
local function loadTemplateStyles()
	local frame = mw.getCurrentFrame()
	
	local hlist_templatestyles = ''
	if lists.hlist_t.found then
		hlist_templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = lists.hlist_t.styles }
		}
	end
	
	local plainlist_templatestyles = ''
	if lists.plainlist_t.found then
		plainlist_templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = lists.plainlist_t.styles }
		}
	end
	
	-- See function description
	local base_templatestyles = frame:extensionTag{
		name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' }
	}

	local templatestyles = ''
	if args['templatestyles'] then
		templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = args['templatestyles'] }
		}
	end
	
	local child_templatestyles = ''
	if args['child templatestyles'] then
		child_templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = args['child templatestyles'] }
		}
	end
	
	local grandchild_templatestyles = ''
	if args['grandchild templatestyles'] then
		grandchild_templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = args['grandchild templatestyles'] }
		}
	end
	
	return table.concat({
		-- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering.
		-- this ordering is not a guarantee because the rows of interest invoking
		-- each class may not be on a specific page
		hlist_templatestyles,
		plainlist_templatestyles,
		base_templatestyles,
		templatestyles,
		child_templatestyles,
		grandchild_templatestyles
	})
end

-- common functions between the child and non child cases
local function structure_infobox_common()
	renderSubheaders()
	renderImages()
	preprocessRows()
	renderRows()
	renderBelowRow()
	renderNavBar()
	renderItalicTitle()
	renderEmptyRowCategories()
	renderTrackingCategories()
	cleanInfobox()
end

-- Specify the overall layout of the infobox, with special settings if the
-- infobox is used as a 'child' inside another infobox.
local function _infobox()
	if args.child ~= 'yes' then
		root = mw.html.create('table')

		root
			:addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox')
			:addClass(args.bodyclass)
			-- @deprecated next; target .infobox-<name>
			:cssText(args.bodystyle)
		
		has_list_class({ args.bodyclass })

		renderTitle()
		renderAboveRow()
	else
		root = mw.html.create()

		root
			:wikitext(args.title)
	end
	structure_infobox_common()
	
	return loadTemplateStyles() .. root
end

-- If the argument exists and isn't blank, add it to the argument table.
-- Blank arguments are treated as nil to match the behaviour of ParserFunctions.
local function preprocessSingleArg(argName)
	if origArgs[argName] and origArgs[argName] ~= '' then
		args[argName] = origArgs[argName]
	end
end

-- Assign the parameters with the given prefixes to the args table, in order, in
-- batches of the step size specified. This is to prevent references etc. from
-- appearing in the wrong order. The prefixTable should be an array containing
-- tables, each of which has two possible fields, a "prefix" string and a
-- "depend" table. The function always parses parameters containing the "prefix"
-- string, but only parses parameters in the "depend" table if the prefix
-- parameter is present and non-blank.
local function preprocessArgs(prefixTable, step)
	if type(prefixTable) ~= 'table' then
		error("Non-table value detected for the prefix table", 2)
	end
	if type(step) ~= 'number' then
		error("Invalid step value detected", 2)
	end

	-- Get arguments without a number suffix, and check for bad input.
	for i,v in ipairs(prefixTable) do
		if type(v) ~= 'table' or type(v.prefix) ~= "string" or
			(v.depend and type(v.depend) ~= 'table') then
			error('Invalid input detected to preprocessArgs prefix table', 2)
		end
		preprocessSingleArg(v.prefix)
		-- Only parse the depend parameter if the prefix parameter is present
		-- and not blank.
		if args[v.prefix] and v.depend then
			for j, dependValue in ipairs(v.depend) do
				if type(dependValue) ~= 'string' then
					error('Invalid "depend" parameter value detected in preprocessArgs')
				end
				preprocessSingleArg(dependValue)
			end
		end
	end

	-- Get arguments with number suffixes.
	local a = 1 -- Counter variable.
	local moreArgumentsExist = true
	while moreArgumentsExist == true do
		moreArgumentsExist = false
		for i = a, a + step - 1 do
			for j,v in ipairs(prefixTable) do
				local prefixArgName = v.prefix .. tostring(i)
				if origArgs[prefixArgName] then
					-- Do another loop if any arguments are found, even blank ones.
					moreArgumentsExist = true
					preprocessSingleArg(prefixArgName)
				end
				-- Process the depend table if the prefix argument is present
				-- and not blank, or we are processing "prefix1" and "prefix" is
				-- present and not blank, and if the depend table is present.
				if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then
					for j,dependValue in ipairs(v.depend) do
						local dependArgName = dependValue .. tostring(i)
						preprocessSingleArg(dependArgName)
					end
				end
			end
		end
		a = a + step
	end
end

-- Parse the data parameters in the same order that the old {{infobox}} did, so
-- that references etc. will display in the expected places. Parameters that
-- depend on another parameter are only processed if that parameter is present,
-- to avoid phantom references appearing in article reference lists.
local function parseDataParameters()

	preprocessSingleArg('autoheaders')
	preprocessSingleArg('child')
	preprocessSingleArg('bodyclass')
	preprocessSingleArg('subbox')
	preprocessSingleArg('bodystyle')
	preprocessSingleArg('title')
	preprocessSingleArg('titleclass')
	preprocessSingleArg('titlestyle')
	preprocessSingleArg('above')
	preprocessSingleArg('aboveclass')
	preprocessSingleArg('abovestyle')
	preprocessArgs({
		{prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}}
	}, 10)
	preprocessSingleArg('subheaderstyle')
	preprocessSingleArg('subheaderclass')
	preprocessArgs({
		{prefix = 'image', depend = {'caption', 'imagerowclass'}}
	}, 10)
	preprocessSingleArg('captionstyle')
	preprocessSingleArg('imagestyle')
	preprocessSingleArg('imageclass')
	preprocessArgs({
		{prefix = 'header'},
		{prefix = 'data', depend = {'label'}},
		{prefix = 'rowclass'},
		{prefix = 'rowstyle'},
		{prefix = 'rowcellstyle'},
		{prefix = 'class'}
	}, 50)
	preprocessSingleArg('headerclass')
	preprocessSingleArg('headerstyle')
	preprocessSingleArg('labelstyle')
	preprocessSingleArg('datastyle')
	preprocessSingleArg('below')
	preprocessSingleArg('belowclass')
	preprocessSingleArg('belowstyle')
	preprocessSingleArg('name')
	-- different behaviour for italics if blank or absent
	args['italic title'] = origArgs['italic title']
	preprocessSingleArg('decat')
	preprocessSingleArg('templatestyles')
	preprocessSingleArg('child templatestyles')
	preprocessSingleArg('grandchild templatestyles')
end

-- If called via #invoke, use the args passed into the invoking template.
-- Otherwise, for testing purposes, assume args are being passed directly in.
function p.infobox(frame)
	if frame == mw.getCurrentFrame() then
		origArgs = frame:getParent().args
	else
		origArgs = frame
	end
	
	parseDataParameters()
	
	return _infobox()
end

-- For calling via #invoke within a template
function p.infoboxTemplate(frame)
	origArgs = {}
	for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end
	
	parseDataParameters()
	
	return _infobox()
end
return p