Module:Infobox: Difference between revisions

From OtherX
Jump to navigation Jump to search
No edit summary
Tag: Reverted
No edit summary
 
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- Module:Infobox
--[=[
-- For documentation, see [[Module:Infobox/doc]]
--]=]


-- <nowiki>
local Infobox = {}
local Infobox = {}
Infobox.__index = Infobox
Infobox.__index = Infobox
Line 7: Line 10:
-- Edit button for unknown params
-- Edit button for unknown params
local editbutton = require('Module:Edit button')
local editbutton = require('Module:Edit button')
local var = mw.ext.VariablesLua
local edit = editbutton("'''?''' (edit)")
local edit = editbutton("'''?''' (edit)")
-- Page title
local pagename = mw.title.getCurrentTitle().fullText
-- map of flags to html tags used by Infobox.addRow()
-- let's only define it once, since :addRow() is used multiple times per module
local tagmap = {
tr = 'tr',
th = 'th',
td = 'td',
argh = 'th',
argd = 'td'
}
--[=[
-- Standardised functions
-- called as string with defineParams
--]=]


-- Standardised "has content" function
-- Standardised "has content" function
function hasContent(arg, default)
function hasContent(arg, default)
    return string.match(arg or '','%S') and arg or default
-- Return arg if any non-whitespace character is found
return string.match(arg or '','%S') and arg or default
end
end


function Infobox.new(args)
    local self = setmetatable({}, Infobox)
    self.args = args
    -- Initialize other properties as needed
    return self
end


-- defineParams method to set the parameters
-- Standardised "name" function
function Infobox.defineParams(self, params)
function subjectName(arg)
    self.params = params
return string.match(arg or '','%S') and arg or nil
end
end


-- Standardised "release" function
-- Create a standardised release function, since so many pages use it
-- Standardised "release" function for dates
-- Turns release and update into a single parameter
function releaseUpdate(release, update)
function releaseUpdate(release, update)
    -- Assuming release dates are entered in a valid format, return as is
if not Infobox.isDefined(release) then
    -- Add any additional formatting or checks if needed
return nil
    return release
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
end


-- Standardised "image" function for .png images
-- Standardised image function
function image(img)
function image(img)
    -- Check if the image name ends with '.png', if not, add it
if img and img:find('%S') then
    if img and not img:match('%.png$') then
return img
        img = img .. '.png'
else
    end
return nil
    return img
end
end
end


-- Standardised "numbers" function for numeric values
-- Standardised numbers
function numbers(num)
function numbers(num)
     -- Convert to number if possible, else return nil or default value
num = string.gsub(num or '',',','')
    return tonumber(num)
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
else
return false
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
 
--[[
Creates an infobox
-- 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 self.switchfo then
-- Buttons wrapper
-- Hidden by default, unhidden by javascript
self.switch_buttons_tag = mw.html.create('div')
:addClass('infobox-buttons')
 
-- default version to immediately switch to via js
local defv = tonumber(self.args.defver)
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
 
local numlines = 1
local width_working = 0
local total_width = 0
local buttons = {}
-- Add individual buttons to the wrapper
for i=1,self.versions do
local wid = button_width(self.labels[i] or i)
width_working  = width_working + wid
total_width = total_width + wid
if width_working > LINE_WIDTH then
numlines = numlines + 1
width_working = wid
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
local best = {-1, 100000}
if (numlines > 1) and (numlines <= MAX_LINES) then
-- attempt to balance line widths
local w_s, w_e = 0,total_width
for i = 1,#buttons-1 do
w_s = w_s + buttons[i][2]
w_e = w_e - buttons[i][2]
if w_s > LINE_WIDTH then
-- w_s only increases, so we're done once it exceeds the width
break
end
if w_e <= LINE_WIDTH then
-- w_e only decreases, so just continue if it exceeds line
local diff = math.abs(w_s - w_e)
if diff < best[2] then
best = { i, diff }
end
end
end
if best[1] == -1 then
best = { math.floor(#buttons/2), 100000 }
end
end
for i,v in ipairs(buttons) do
self.switch_buttons_tag:node(v[1])
if i == best[1] then
self.switch_buttons_tag:tag('span'):addClass('line-break')
end
end
-- Used by JavaScript to turn the buttons into a menu list if too many variants
if self.versions > self.maxbuttons or numlines > MAX_LINES then
self.switch_buttons_tag:addClass('infobox-buttons-select')
end
self.switch_buttons_tag:done()
end
 
-- Create infobox table
self.rtable = mw.html.create('table')
if self.addrswibclass then
self.rtable:addClass('infobox')
end
-- Add necessary class if switch infobox
if self.switchfo then
self.rtable:addClass('infobox-switch')
end
 
end
 
-- Defines an infobox name ({{Template:arg}})
-- Used to create a link at the bottom of pages
function Infobox:defineName(arg)
self.infoboxname = arg
end
 
-- Defines the bottom links of the infobox
-- pass a table whose elements are tables that define a link and a label
-- {
-- { 'link', 'label },
-- ...
-- }
-- The template name can be substituted into the tables using '%s'
-- If we wanted Template:InFooBar to link to it's /doc page with a "doc" label:
-- { ...
-- { 'Template:%s/doc', 'doc' },
-- ... }
-- 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
 
-- Change max number of buttons before switching to menu
-- defaults to 5
-- MUST BE RUN BEFORE :create()
function Infobox:setMaxButtons(arg)
-- if not a number, just go back to default
self.maxbuttons = tonumber(arg) or DEFAULT_MAX_BUTTONS
end
 
--[[
Add parameters functions
All parameters should be tables
The first parameter defines the type of cell to create
-- th : <th>
-- td : <td>
-- argh : <th>
-- argd : <td>
The second parameter defines what is inside the tag
-- th | th : text passed
-- argh | argd : parameter with the name passed
Additional named parameters can be used to add any styling or attributes
-- attr : mw.html:attr({ arg1 = '1', ... })
-- css : mw.html:css({ arg1 = '1', ...)
-- class : mw.html:addClass('arg')
---- class also supports a table of values, even though mw.html:addClass() does not
-- rowspan : mw.html:attr('rowspan',arg)
-- colspan : mw.html:attr('colspan',arg)
-- title : mw.html:attr('title',arg)
Example:
ipsobox:addRow( { 'th' , 'Header', title = 'Title' },
{ 'argh', 'arg1', class = 'parameter' } })
produces:
<tr><th title="Title">Header</th><th class="parameter">args.arg1</th></tr>
 
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
if v.colspan then
_cell:attr('colspan',v.colspan)
end
if v.rowspan then
_cell:attr('rowspan',v.rowspan)
end
if v.title then
_cell:attr('title',v.title)
end
if v.css then
_cell:css(v.css)
end
 
-- if class is a string, it can be added directly
-- if a table, add every value
-- mw.html:addClass() doesn't function with tables
-- so iterate over the class names here and add them individually
if v.class then
if type(v.class) == 'string' then
_cell:addClass(v.class)
elseif type(v.class) == 'table' then
for _, w in ipairs(v.class) do
_cell:addClass(w)
end
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
 
-- not that meta
-- allow classes to be defined on the whole row
-- okay, sort of meta
if args.meta then
if args.meta.addClass then
_row:addClass(args.meta.addClass)
end
end
 
return box
end
 
function Infobox.customButtonPlacement(box,arg)
box.custom_buttons = arg
return box
end
 
-- Choose whether to set [[Property:Default version]]
-- Defaults to false.
function Infobox.setDefaultVersionSMW(box, arg)
box.set_default_version_smw = arg
return box
end
 
function Infobox.addButtonsRow(box, args)
if box.switchfo then
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
 
--[[
-- adds a blank row of padding spanning the given number of columns
--]]
function Infobox.pad(box, colspan, class)
local tr = box:tag('tr')
:tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding')
:done()
if class then
tr:addClass(class)
end
tr:done()
return box
end
 
--[[
-- functions the same as mw.html:wikitext() on the wrapper
-- Should only be used for categories really
--]]
function Infobox.wikitext(box, arg)
box.rtable:wikitext(arg)
return box
end
 
--[[
-- Adds the specified item(s) to the end of the infobox, outside of the table
-- items are concatenated together with an empty space
--]]
function Infobox.append(box, ...)
for i,v in ipairs({...}) do
table.insert(box.appendStrs, v)
end
return box
end
 
--[[
-- Adds a caption to the infobox
-- defaults to the pagename
-- 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
 
local caption = box.rtable:tag('caption')
:wikitext(name)
 
-- add necessary attribute for switch infoboxes
if box.switchfo then
caption:attr('data-attr-param','name')
end
 
return box
end
 
--[[
-- Functions for styling the infobox
-- works the same as the respective mw.html functions
--]]
-- attr
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
return box
end
 
--[[
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
 
--[[
Allows the infobox to use Semantic Media Wiki and give parameters properties
Pass a table to this function to map parameter names to properties
 
Calling syntax:
-- {{#show:page|?property}}:
-- "<property>" - unqualified and without a number will display the default value
-- "<property#>" - with a number will show the switch data from that index
-- "all <property>" - adding all will display every unique value in a comma separated list
 
Properties initiated in Infobox:finish()
--]]
function Infobox:useSMW(arg)
if type(arg) == 'table' then
for w, v in pairs(arg) do
self._smw[w] = v
end
end
end
--[[
As above, but only assigns to "<property>", which will act like "all <property>" - "<property>#" not present
 
Properties initiated in Infobox:finish()
--]]
function Infobox:useSMWOne(arg)
if type(arg) == 'table' then
for w, v in pairs(arg) do
self._smwOne[w] = v
end
end
end
--[[
     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
 
function Infobox:useSMWElement(arg)
if type(arg) == 'table' then
for w, v in pairs(arg) do
self._smwElement[w] = v
end
self.setSMWElement = true
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
self.switch_tag:tag('span')
:wikitext(string.format('Versions: [[Version count::%s]]',self.versions))
: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
if onContentNamespace and not self.suppressAllSMW then
-- members smw display, yes --> true; no --> false; other --> unknown
local function smwMembers(smw_arg)
local smw_argv = string.lower(smw_arg or '')
if smw_argv == 'yes' then
return 'true'
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
for i,v in pairs(smw_data_arr_elem) do
for j,k in ipairs(v) do
if k ~= 'unknown' then
self.smw_error_tag:tag('span'):wikitext(mw.ustring.format('%s: [[%s::%s]]', i,i,k ))
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
-- Define as finished
self.__finished = true
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
 
--[[
-- Forces an infobox to only use 1 variant
-- Mainly used by lite infoboxes
-- This should be run before creation
--]]
function Infobox:noSwitch()
self.versions = 1
self.switchfo = false
end
 
--[[
-- Calculates the max version
-- Adds labels
-- Sees if this needs to be a switch infobox
-- Returns extra version count (even if already run)
--]]
function Infobox.maxVersion(box)
-- Only allowed to run once
if box.versions ~= -1 then
return box.versions
end
 
box.labels = {}
box.versions = 0
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
local label = box.args['version'..i] or ('Version '..i)
table.insert(box.labels,label)
-- add to smwname
if box.args['smwname'..i] then
table.insert(smwname, mw.text.split(box.args['smwname'..i], '¦'))
else
table.insert(smwname, {label})
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
if box.versions > 0 then
box.switchfo = true
box._smwSubobjectName = smwname
else
-- single version, check for smwname
if box.args['smwname'] then
box._smwSubobjectName = {mw.text.split(box.args['smwname'], '¦')}
end
end
 
-- versions calculated
return box.versions
end
 
--[[
-- Cleans parameters as defined by the above function
-- SHOULD NOT BE RUN UNTIL ALL THINGS ARE DEFINED
-- Handles switches as well
-- adds table _add to rargs, a cleaned up version of arguments
-- d : default value
-- switches : table of switches (in numerical order)
-- 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
 
-- mass dupe removal
-- this needs to be done at the end to keep dependent parameters working
-- also removes incompatible data types
for _, v in ipairs(self.paramnames) do
-- not removed from dupe enabled params parameters
if not self.dupeable[v] and self.rargs[v] and self.rargs[v].switches then
-- tells us whether or not we'll need to remove the switch data
-- 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
 
-- Title parentheses (has to be done here, sadly)
local _name
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
if _name == mw.title.getCurrentTitle().fullText then
self.rtable:addClass('no-parenthesis-style')
end
end
end
 
--[[
Function to link internal use parameters with JS class attribution
-- self:linkParams{ { arg1, arg2 }, ... { arg1a, arg2a } }
-- arg1: parameter name being linked
-- arg2: parameter name that holds the classes
-- THIS FUNCTION SHOULD BE RUN AFTER :cleanParams()
-- THIS FUNCTION SHOULD BE RUN BEFORE :finish()
-- The second argument's data should always contain a value (a CSS class name) at every index
-- This function is cancelled for non switch boxes
--]]
function Infobox:linkParams(...)
if not self.switchfo then
return
end
for _, v in ipairs(...) do
self.switchfoattr[v[1]] = self.rargs[v[2]]
end
end
 
--[==========================================[
-- Functions for accessing parameters easily
--]==========================================]
--[[
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 param = self.rargs[arg]
-- Return nil if no table found
if not param then return nil end
 
-- Return default
if ret_func == 'd' then
return param.d
end
 
-- Return full table
if ret_func == 'f' then
return param
end
 
-- Return switch table
if ret_func == 's' then
return param.switches
end
 
-- 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
 
--[[
Checks if a parameter is defined and not blank
-- arg : parameter to look at
-- index : index of switches to look at (defaults to default param)
-- defining 'all' will look at every possible value for the parameter
--]]
function Infobox:paramDefined(arg,index)
-- Can use cleaned params for switches
-- but need the passed to identify blanks in the template
local param = self.rargs[arg]
local _arg = self.args[arg]
if string.find(_arg or '','%?action=edit') then
_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
 
--[[
Function to perform a search on all parameters of a defined name
-- param: param name
-- val: a value or function
-- functions passed must return either true or false
-- 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
local valtype = type(val)
-- start with the default value
-- if it's a function, run it
if valtype == 'function' then
ret = val(arg.d)
 
-- true means it matched
if ret == true then
return ret
end
 
-- 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
-- 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
 
-- switch cases
-- more organised putting them here
if arg.switches then
for _, v in ipairs(arg.switches) do
if valtype == type(v) then
if valtype == 'string' then
if val:lower() == v:lower() then
return true
end
elseif val == v then
return true
end
end
end
end
 
-- return false in every other case
return false
end
------
 
function Infobox.paramRead(arg,val)
-- if no parameters, return nil
if not arg then
return nil
end
 
local ret
local valtype = type(val)
-- start with the default value
-- if it's a function, run it
if valtype == 'function' then
ret = val(arg.d)
 
-- true means it matched
if ret == true then
return ret
end
 
-- 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
-- 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
 
-- switch cases
-- more organised putting them here
if arg.switches then
for _, v in ipairs(arg.switches) do
if valtype == type(v) then
if valtype == 'string' then
if val:lower() == v:lower() then
return true
end
elseif val == v then
return true
end
end
end
end
 
-- return false in every other case
return false
end
 
----
 
-- Return collected category data
function Infobox:categoryData()
return self.catdata
end
 
-- Infobox:addDropLevelVars("thieving", "skilllvl1")
function Infobox:addDropLevelVars(key, paramName)
local levelParams = self:param(paramName, 'f')
local dropParams = self:param('dropversion', 'f')
if levelParams == nil then
return
end
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'
else
dropVersionFromInfobox = dropVersionFromInfobox .. ",DEFAULT"
end
for dropVersion in string.gmatch(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
-- 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 v in string.gmatch(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
 
-- Override tostring
function Infobox.tostring(box)
-- If not finished, finish
if not box.__finished then
box:finish()
end
 
-- Make entire html wrapper a string and return it
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 Infobox
return Infobox
-- </nowiki>

Latest revision as of 21:54, 21 January 2024

Module documentation
This documentation is transcluded from Module:Infobox/doc. [edit] [history] [purge]
Module:Infobox requires Module:Edit button.

Template:ToC

Creating a template step-by-step

Import Module:Infobox

<syntaxhighlight lang="lua"> local infobox = require('Module:Infobox') </syntaxhighlight>

Unpack the frame arguments

<syntaxhighlight lang="lua"> function p.main(frame) local args = frame:getParent().args ... </syntaxhighlight>

Define an Infobox object

<syntaxhighlight lang="lua"> local ret = infobox.new(args) </syntaxhighlight>

Map your arguments to functions

<syntaxhighlight lang="lua"> ret:defineParams { ... } </syntaxhighlight>

Parse your arguments

<syntaxhighlight lang="lua"> ret:cleanParams() </syntaxhighlight>

Initialise your HTML

<syntaxhighlight lang="lua"> ret:create() </syntaxhighlight>

Name your infobox

<syntaxhighlight lang="lua"> -- note: we don't use the "Template:" namespace prefix ret:defineName('Infobox FooBar') </syntaxhighlight>

Give your infobox custom behaviour

<syntaxhighlight lang="lua"> ret:addClass(...) ret:useSMW({ ... }) ret:defineLinks({ ... }) </syntaxhighlight>

Give your infobox a caption

<syntaxhighlight lang="lua"> ret:caption() </syntaxhighlight>

Format your table

<syntaxhighlight lang="lua"> ret:addRow{ ... } :addRow{ ... } </syntaxhighlight>

Finalise your template

<syntaxhighlight lang="lua"> ret:finish() </syntaxhighlight>

Functions

Public functions

For the example snippets below, ret is an arbitrary infobox initiated as: <syntaxhighlight lang="lua"> local ret = Infobox.new(args) </syntaxhighlight>

Priority

Certain functions rely on information created by other functions, and thus must be run in a particular order:

0
  • new
1
  • defineParams
2
  • create
  • maxVersion
3
  • cleanParams
  • setMaxButtons
  • noSwitch
4
  • other functions
5
  • finish

nilParam

<syntaxhighlight lang="lua"> Infobox.nilParam() </syntaxhighlight> Returns the value of the placeholder value Infobox.nil_param. This value is generally not seen, but it is used to fill switchbox data as nil causes unwanted behaviour.

isDefined

<syntaxhighlight lang="lua"> Infobox.isDefined(arg) </syntaxhighlight> Checks whether the value arg can be considered as defined.

new

<syntaxhighlight lang="lua"> Infobox.new(args) </syntaxhighlight> Creates a new infobox object with args as a table of values passed from the template invocation.

This function also creates the top-level wrapper and sets a metatable for the infobox.

create

<syntaxhighlight lang="lua"> ret:create() </syntaxhighlight> Creates the HTML tags used for the infobox itself. Will run maxVersion() if it has not been run already.

defineName

<syntaxhighlight lang="lua"> ret:defineName(arg) </syntaxhighlight> Defines the name of the infobox. This should be the base pagename; i.e. no "Template:" namespace prefix.

defineLinks

<syntaxhighlight lang="lua"> ret:defineLinks(tbl) </syntaxhighlight> Defines any number of infobox bottom links in a table whose elements are table variables formatted as such:

<syntaxhighlight lang="lua"> { { link, label }, ... } </syntaxhighlight>

link and label should be string variables that will, in a basic sense, define [[Link|label]].

In a more technical sense, link and label help form a string for string.format(), with the module using the equivalent of (but not exactly): <syntaxhighlight lang="lua"> string.format('&#91;'..label..'&#93;',...) </syntaxhighlight>

The template name itself can be called up to 5 times by using "%s", in either link or label.

setMaxButtons

<syntaxhighlight lang="lua"> ret:setMaxButtons(n) </syntaxhighlight> Changes the behaviour of buttons by defining the maximum number of buttons that are allowed to appear. If the version count exceeds the button count, their buttons will be replaced with a drop down menu. If not run, the maximum button count will be 5.

cleanParams

<syntaxhighlight lang="lua"> ret:cleanParams() </syntaxhighlight> Parses the parameters with their mapped functions.

defineParams

<syntaxhighlight lang="lua"> ret:defineParams{ ... } </syntaxhighlight> Maps parameters to functions as defined by a table formatted as such:

<syntaxhighlight lang="lua"> { name = <param>, func = <func>, dupes = true }, </syntaxhighlight>

param should be a string that names the parameter as it will be in references for use in other functions.

func should be a function or instructions on how to find a function.

dupes is a meta configuration that allows the parameter to be duplicated in switch data. Normally, these duplicate values are not needed, and they will be deleted if they are the same as the default values after all parameters have been parsed. Some parameters require duplication to function properly, such as display altering parameters defined with linkParams().

If duplicates are not needed, this parameter should be deleted.

Predefined functions

If func is a string, then the module will attempt to use a predefined function.

Function Use
name Uses the parameter named "name". If "name" is blank or undefined, it will use the page name.
release Uses the parameters named "release" and "update" and formats it as such:
  • If both are defined: <release> ([[Update:<update>|Update]])
  • If only release is defined: <release> (Update unknown)
  • If release is not defined: ? (edit)
removal Uses the parameters named "removal" and "removalupdate" and formats it the same as release
hasContent If the parameter is defined and not blank, it will be used. Otherwise, ? (edit) will be used.
image Currently functions the same as hasContent
numbers Removes commas from the parameter and attempts to cast it to a number. If it works, the number is used, otherwise ? (edit)
User-defined functions

If func is a function, then that function will be used to parse the parameter. User-defined functions allow for more robust formatting and guidelines.

As a general practice, user-defined functions should be located under the main functions in the module. It is best to document these functions so that they may be changed by other editors if necessary.

In user-defined functions, circumstances where the edit button should be shown should return nil, which is automatically handled by the module.

Simple and complex definitions

Parameters may be mapped to functions in a straightforward manner by simply definining a name of or reference to a function, such as: <syntaxhighlight lang="lua"> ret:defineParams{ { name = 'foo', func = 'hasContent' }, { name = 'bar', func = barFunc } } </syntaxhighlight>

Simple definitions only pass the parameter from the master list named <name>. Some parameters need more interaction with other parameters. To do this, we require a complex definition. Complex definitions point to a user-defined function and map what parameters to pass. Complex definitions can also pass literal values or the uncleaned value with the use of flags.

Complex functions are defined as table values formatted as such: <syntaxhighlight lang="lua"> func = { name = <funcname>, params = { <paramname>, ... }, flag = <flags> } </syntaxhighlight>

A basic example for complex functions is: <syntaxhighlight lang="lua"> ret:defineParams{ { name = 'foo', func = { name = fooFunc, params = { 'bar', 'baz' } }, } -- ... -- ... function fooFunc(arg1,arg2) return arg1..arg2 end </syntaxhighlight>

In this example, we have a parameter named "foo", but we use the parameters passed to the template named "bar" and "baz". Parameters are passed in the order listed, so in the above, the parameter bar is used for arg1, and baz is used for arg2.

Flags define the behaviour of the parameters named. The following flag behaviours exist:

  • d - Looks for the cleaned and parsed version of the parameter. If not cleaned, it will use the value passed to the template. If neither exist, it will use nil. This is the default behaviour if no flag is defined.
  • p - Looks only at the value passed to the template. If it does not exist, it will use nil.
  • r or l - Uses the literal (or raw) value.

If flag is a string, then the behaviour defined by it will apply to every parameter passed. If flag is a table, then each flag will only define the behaviour of the respective parameter.

For example: <syntaxhighlight lang="lua"> ret:defineParams{ { name = 'foo', func = { name = fooFunc, params = { 'foo', 'bar', 'baz' }, flag = { 'd', 'p', 'l' }, }, } </syntaxhighlight>

In the above snippet, foo will use the default behaviour and bar will only look for the value passed to the template. The third parameter will use the string literal 'baz'.

Definition order

Parameters are defined in the order that they are coded. If a parameter relies on the cleaned value of another parameter, then the parameter dependent on the other will need to be defined after in the definition table.

addRow

<syntaxhighlight lang="lua"> ret:addRow(tbl) </syntaxhighlight> Adds a new row to the template with columns and behaviour defined by tbl, which should be a table that holds cells defined as table variables, formatted as such:

<syntaxhighlight lang="lua"> { { celltype, label, <attr1> = <value1>, <attr2> = <value2> ... }, ... } </syntaxhighlight>

celltype and label are string values that define the fundamental display of the cell.

celltype Output
th Creates a <th> tag where the content is the value of label
td Creates a <td> tag where the content is the value of label
argh Creates a <th> tag where the content is the value of the parameter named label
argd Creates a <td> tag where the content is the value of the parameter named label

The attributes are any of the available attributes defined inside the function. All functions that are ultimately run are documented in the Lua reference manual and are run on the tag for the specific table cell they are called on.

attr Type Use
attr table Passes the value to mw.html.attr()
css table Passes the value to mw.html.css()
colspan number Uses the value to run mw.html.attr('colspan',#)
rowspan number Uses the value to run mw.html.attr('rowspan',#)
title string Uses the value to run mw.html.attr('title',text)
class string Passes the value to mw.html.addClass()
class table Passes every value in the table to mw.html.addClass()

If the template is a switch infobox, then data-attr-param="<paramname>" will be added to the table cell.

This function will return the template, allowing further self functions to be performed.

wikitext

<syntaxhighlight lang="lua"> ret:wikitext(txt) </syntaxhighlight> Appends wikitext to the top-level wrapper. Templates, etc. passed directly from Lua code will need explicit preprocessing prior to display properly.

This function will return the template, allowing further self functions to be performed.

caption

<syntaxhighlight lang="lua"> ret:caption() </syntaxhighlight> Adds a caption to the infobox based off the subject name, using the following priority for the text:

  • name parameter
  • name1 parameter (if switch infobox)
  • {{PAGENAME}}

If the template is a switch infobox, this will also add data-attr-param="name" to the caption.

This function will return the template, allowing further self functions to be performed.

attr

<syntaxhighlight lang="lua"> ret:attr(arg) </syntaxhighlight> Passes arg to mw.html.attr().

This function will return the template, allowing further self functions to be performed.

float

<syntaxhighlight lang="lua"> ret:float(dir) </syntaxhighlight> Changes the direction of the CSS style float for the top level wrapper. By default, all infoboxes float right.

This function will return the template, allowing further self functions to be performed.

css

<syntaxhighlight lang="lua"> ret:css(...) </syntaxhighlight> Passes the arguments to mw.html.css().

This function will return the template, allowing further self functions to be performed.

addClass

<syntaxhighlight lang="lua"> ret:addClass(arg) </syntaxhighlight> Passes arg to mw.html.addClass().

This function will return the template, allowing further self functions to be performed.

addClasses

<syntaxhighlight lang="lua"> ret:attr(...) </syntaxhighlight> Passes every argument to mw.html.addClass() individually.

This function will return the template, allowing further self functions to be performed.

tag

<syntaxhighlight lang="lua"> ret:tag(arg) </syntaxhighlight> Passes arg to mw.html.tag().

This function will return the tag (not the template), allowing further self functions to be performed.

useSMW

<syntaxhighlight lang="lua"> ret:useSMW(tbl) </syntaxhighlight> Tells the module to create properties for parameters, as well as defining those mappings with tbl, a table whose elements form an associated array formatted as such:

<syntaxhighlight lang="lua"> { parameter = property, ... } </syntaxhighlight>

When defined, properties will be defined for two separate properties: both "Property:<name>" and "Property:All <name>". If the template holds switch infobox data, then properties will be defined for "Property:<name><X>" for all applicable X. The "All <name>" properties exist to create storage for all applicable values. Keep this in mind, as "Property:<name>" will only ever store the default value.

By default, the module will define Property:Release date and Property:Is members only.

noSwitch

<syntaxhighlight lang="lua"> ret:noSwitch() </syntaxhighlight> Forces the template to use only a single version, the default.

maxVersion

<syntaxhighlight lang="lua"> ret:maxVersion() </syntaxhighlight> Returns the number of versions used by the infobox. When run the first time, this function will check and define the version count first. Subsequent calls will simply return the count.

linkParams

<syntaxhighlight lang="lua"> ret:linkParams{ paramName = linkedParam, ... } </syntaxhighlight> Links two parameters where one parameter (paramName) is the parameter name of a cell's contents, and linkedParam is the name of the parameter that contains the classes for the specified cell. It will only have an effect on switch infoboxes. Both parameters will need to be defined in the infobox with definedParams. They should be functions of the parameter they operator on and include dupes = true in their definitions.

This function must be called only after calling ret:cleanParams() (and before ret:finish()).

Example: grand exchange graphs do not make sense to display for untradeable items within switch infoboxes (e.g. ahrim's staff); one could use a linkParam to add a class that will display:none table row when switching to untradeable items.

In the source code of the infobox, these will be added as such:

<syntaxhighlight lang="html5">

</syntaxhighlight>

From this, the switch infobox javascript will add the contents of data-addclass to class attribute of the row of the table when the infobox is switched. You will also need to define the classes you are using in global CSS.

If the parameter is a th or td element, the class is added to the parent tr. Otherwise, it is added to the element directly (e.g. caption element).

finish

Finalises the template and adds remaining required HTML.

param

<syntaxhighlight lang="lua"> ret:param(arg, flag) </syntaxhighlight> Returns the value of the parameter named arg. The exact format and values are determined by the flag:

  • d or empty - default value of the parameter
  • f or full - all values of the parameter as a table
  • s or switches - table of all switch values (or nil if no switches are present)
  • s# - switch value at index #
  • r - if defined, switch value 1, otherwise the default value

Note that this function returns the actual table used by the template. If a switch value is equal to Infobox.nil_param for flag s#, then nil will be returned instead; however, when calling specific indices from the tables returned by f or r, the value of Infobox.nil_param will be returned without extra parsing.

paramDefined

<syntaxhighlight lang="lua"> ret:paramDefined(arg, flag) </syntaxhighlight> Looks to see if the parameter named arg is defined at any index, based on instructions from flag:

  • 0 or empty - default value of parameter
  • # - value of switch at index #
  • all - true if the parameter is defined with a default or at any switch value

paramGrep

<syntaxhighlight lang="lua"> ret:paramGrep(arg, query) </syntaxhighlight> Performs a function or search on every possible value of the parameter named arg. query can be either a function, a string, or a number. If a match is found or the function query returns true, paramGrep will end its search.

If query is a function, that function must pass a single argument (the parameter), and return either true or false.

If query is a string, then that string will be compared to each value of the parameter. If they match exactly, disregarding casing, then true will be returned. To perform a pattern match on strings, you will need to define query as a function that returns a boolean value based on string.find or mw.ustring.find.

If query is a number (or any other type), then it will be compared to each value for a match.

Matches are only checked if the type of query matches the value of the parameter; e.g., it is not valid to search for a number (ex: 4) inside a string (ex: '455').

paramRead

<syntaxhighlight lang="lua"> Infobox.paramRead(arg, query) </syntaxhighlight> Performs the same function as paramGrep; however, instead of arg being a name of a parameter to look for, it is the table itself. Useful for functions where only the table of parameters is passed, but not the whole infobox.

categoryData

<syntaxhighlight lang="lua"> ret:categoryData() </syntaxhighlight> Returns fundamental category data collected during Infobox:cleanParams().

Fields in the category data include:

  • one_defined - true if at least one possible value of the parameter is defined
  • all_defined - false if at least one possible value of the parameter is not defined

addDropLevelVars

<syntaxhighlight lang="lua"> Infobox:addDropLevelVars(key, paramName) </syntaxhighlight> Used to link infobox versions to specific drops table versions. Sets a var DropLevel_{key}_{version_name}, where the value is pulled from the value of the given param paramName at each version. If multiple values are set with the same key, the result will be a comma separated string.

For example, if an infobox has |version1 = A|level1 = 10, then ret:addDropLevelVars('combat', 'level') will set the variable DropLevel_combat_A = 10.

Rules

Defined parameters

Parameters are to be considered "defined" if they meet all of the following criteria:

  • Is not nil
  • Contains at least one non-whitespace character string.find(param,'%S')
  • Is not equal to Infobox.nil_param
  • Does not contain the string 'action=edit'

--[=[
-- For documentation, see [[Module:Infobox/doc]]
--]=]

-- <nowiki>
local Infobox = {}
Infobox.__index = Infobox
Infobox.__tostring = Infobox.tostring

-- Edit button for unknown params
local editbutton = require('Module:Edit button')
local var = mw.ext.VariablesLua
local edit = editbutton("'''?''' (edit)")

-- Page title
local pagename = mw.title.getCurrentTitle().fullText

-- map of flags to html tags used by Infobox.addRow()
-- let's only define it once, since :addRow() is used multiple times per module
local tagmap = {
	tr = 'tr',
	th = 'th',
	td = 'td',
	argh = 'th',
	argd = 'td'
}

--[=[
-- Standardised functions
-- called as string with defineParams
--]=]

-- Standardised "has content" function
function hasContent(arg, default)
	-- Return arg if any non-whitespace character is found
	return string.match(arg or '','%S') and arg or default
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
		else
			return false
		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

--[[
	Creates an infobox
	-- 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 self.switchfo then
		-- Buttons wrapper
		-- Hidden by default, unhidden by javascript
		self.switch_buttons_tag = mw.html.create('div')
					:addClass('infobox-buttons')

		-- default version to immediately switch to via js
		local defv = tonumber(self.args.defver)
		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

		local numlines = 1
		local width_working = 0
		local total_width = 0
		local buttons = {}
		-- Add individual buttons to the wrapper
		for i=1,self.versions do
			local wid = button_width(self.labels[i] or i)
			width_working  = width_working + wid
			total_width = total_width + wid
			if width_working > LINE_WIDTH then
				numlines = numlines + 1
				width_working = wid
			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
		
		local best = {-1, 100000}
		if (numlines > 1) and (numlines <= MAX_LINES) then
			-- attempt to balance line widths
			local w_s, w_e = 0,total_width
			for i = 1,#buttons-1 do
				w_s = w_s + buttons[i][2]
				w_e = w_e - buttons[i][2]
				if w_s > LINE_WIDTH then
					-- w_s only increases, so we're done once it exceeds the width
					break
				end
				if w_e <= LINE_WIDTH then
					-- w_e only decreases, so just continue if it exceeds line
					local diff = math.abs(w_s - w_e)
					if diff < best[2] then
						best = { i, diff }
					end
				end
			end
			if best[1] == -1 then
				best = { math.floor(#buttons/2), 100000 }
			end
		end
		for i,v in ipairs(buttons) do
			self.switch_buttons_tag:node(v[1])
			if i == best[1] then
				self.switch_buttons_tag:tag('span'):addClass('line-break')
			end
		end
		
		-- Used by JavaScript to turn the buttons into a menu list if too many variants
		if self.versions > self.maxbuttons or numlines > MAX_LINES then
			self.switch_buttons_tag:addClass('infobox-buttons-select')
		end
		
		self.switch_buttons_tag:done()
	end

	-- Create infobox table
	self.rtable = mw.html.create('table')
	if self.addrswibclass then
		self.rtable:addClass('infobox')
	end
	-- Add necessary class if switch infobox
	if self.switchfo then
		self.rtable:addClass('infobox-switch')
	end

end

-- Defines an infobox name ({{Template:arg}})
-- Used to create a link at the bottom of pages
function Infobox:defineName(arg)
	self.infoboxname = arg
end

-- Defines the bottom links of the infobox
-- pass a table whose elements are tables that define a link and a label
-- {
--	{ 'link', 'label },
--	...
-- }
-- The template name can be substituted into the tables using '%s'
-- If we wanted Template:InFooBar to link to it's /doc page with a "doc" label:
-- { ...
--	{ 'Template:%s/doc', 'doc' },
-- ... }
-- 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

-- Change max number of buttons before switching to menu
-- defaults to 5
-- MUST BE RUN BEFORE :create()
function Infobox:setMaxButtons(arg)
	-- if not a number, just go back to default
	self.maxbuttons = tonumber(arg) or DEFAULT_MAX_BUTTONS
end

--[[
	Add parameters functions
	All parameters should be tables
	The first parameter defines the type of cell to create
		-- th : <th>
		-- td : <td>
		-- argh : <th>
		-- argd : <td>
	The second parameter defines what is inside the tag
		-- th | th : text passed
		-- argh | argd : parameter with the name passed
	Additional named parameters can be used to add any styling or attributes
		-- attr : mw.html:attr({ arg1 = '1', ... })
		-- css : mw.html:css({ arg1 = '1', ...)
		-- class : mw.html:addClass('arg')
		---- class also supports a table of values, even though mw.html:addClass() does not
		-- rowspan : mw.html:attr('rowspan',arg)
		-- colspan : mw.html:attr('colspan',arg)
		-- title : mw.html:attr('title',arg)
	Example:
		ipsobox:addRow( { 'th' , 'Header', title = 'Title' },
				{ 'argh', 'arg1', class = 'parameter' } })
	produces:
		<tr><th title="Title">Header</th><th class="parameter">args.arg1</th></tr>

	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
		if v.colspan then
			_cell:attr('colspan',v.colspan)
		end
		if v.rowspan then
			_cell:attr('rowspan',v.rowspan)
		end
		if v.title then
			_cell:attr('title',v.title)
		end
		if v.css then
			_cell:css(v.css)
		end

		-- if class is a string, it can be added directly
		-- if a table, add every value
		-- mw.html:addClass() doesn't function with tables
		-- so iterate over the class names here and add them individually
		if v.class then
			if type(v.class) == 'string' then
				_cell:addClass(v.class)
			elseif type(v.class) == 'table' then
				for _, w in ipairs(v.class) do
					_cell:addClass(w)
				end
			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

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

	return box
end

function Infobox.customButtonPlacement(box,arg)
	box.custom_buttons = arg
	return box
end

-- Choose whether to set [[Property:Default version]]
-- Defaults to false.
function Infobox.setDefaultVersionSMW(box, arg)
	box.set_default_version_smw = arg
	return box
end

function Infobox.addButtonsRow(box, args)
	if box.switchfo then
		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

--[[
	-- adds a blank row of padding spanning the given number of columns
--]]
function Infobox.pad(box, colspan, class)
	local tr = box:tag('tr')
			:tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding')
			:done()
	if class then
		tr:addClass(class)	
	end
	tr:done()
	return box
end

--[[
	-- functions the same as mw.html:wikitext() on the wrapper
	-- Should only be used for categories really
--]]
function Infobox.wikitext(box, arg)
	box.rtable:wikitext(arg)
	return box
end

--[[
	-- Adds the specified item(s) to the end of the infobox, outside of the table
	-- items are concatenated together with an empty space
--]]
function Infobox.append(box, ...)
	for i,v in ipairs({...}) do
		table.insert(box.appendStrs, v)
	end
	return box
end

--[[
	-- Adds a caption to the infobox
	-- defaults to the pagename
	-- 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

	local caption = box.rtable:tag('caption')
				:wikitext(name)

	-- add necessary attribute for switch infoboxes
	if box.switchfo then
		caption:attr('data-attr-param','name')
	end

	return box
end

--[[
	-- Functions for styling the infobox
	-- works the same as the respective mw.html functions
--]]
-- attr
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
	return box
end

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

--[[
	Allows the infobox to use Semantic Media Wiki and give parameters properties
	Pass a table to this function to map parameter names to properties

	Calling syntax:
	-- {{#show:page|?property}}:
	-- "<property>" - unqualified and without a number will display the default value
	-- "<property#>" - with a number will show the switch data from that index
	-- "all <property>" - adding all will display every unique value in a comma separated list

	Properties initiated in Infobox:finish()
--]]
function Infobox:useSMW(arg)
	if type(arg) == 'table' then
		for w, v in pairs(arg) do
			self._smw[w] = v
		end
	end
end
--[[
	As above, but only assigns to "<property>", which will act like "all <property>" - "<property>#" not present

	Properties initiated in Infobox:finish()
--]]
function Infobox:useSMWOne(arg)
	if type(arg) == 'table' then
		for w, v in pairs(arg) do
			self._smwOne[w] = v
		end
	end
end
--[[
    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

function Infobox:useSMWElement(arg)
	if type(arg) == 'table' then
		for w, v in pairs(arg) do
			self._smwElement[w] = v
		end
		self.setSMWElement = true
	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
				self.switch_tag:tag('span')
					:wikitext(string.format('Versions: [[Version count::%s]]',self.versions))
				: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
	if onContentNamespace and not self.suppressAllSMW then
		-- members smw display, yes --> true; no --> false; other --> unknown
		local function smwMembers(smw_arg)
			local smw_argv = string.lower(smw_arg or '')
			if smw_argv == 'yes' then
				return 'true'
			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
			for i,v in pairs(smw_data_arr_elem) do
				for j,k in ipairs(v) do
					if k ~= 'unknown' then
						self.smw_error_tag:tag('span'):wikitext(mw.ustring.format('%s: [[%s::%s]]', i,i,k ))
					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
	-- Define as finished
	self.__finished = true
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

--[[
	-- Forces an infobox to only use 1 variant
	-- Mainly used by lite infoboxes
	-- This should be run before creation
--]]
function Infobox:noSwitch()
	self.versions = 1
	self.switchfo = false
end

--[[
	-- Calculates the max version
	-- Adds labels
	-- Sees if this needs to be a switch infobox
	-- Returns extra version count (even if already run)
--]]
function Infobox.maxVersion(box)
	-- Only allowed to run once
	if box.versions ~= -1 then
		return box.versions
	end

	box.labels = {}
	box.versions = 0
	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
			local label = box.args['version'..i] or ('Version '..i)
			table.insert(box.labels,label)
			
			-- add to smwname
			if box.args['smwname'..i] then
				table.insert(smwname, mw.text.split(box.args['smwname'..i], '¦'))
			else
				table.insert(smwname, {label})
			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
	if box.versions > 0 then
		box.switchfo = true
		box._smwSubobjectName = smwname
	else
		-- single version, check for smwname
		if box.args['smwname'] then
			box._smwSubobjectName = {mw.text.split(box.args['smwname'], '¦')}
		end
	end

	-- versions calculated
	return box.versions
end

--[[
	-- Cleans parameters as defined by the above function
	-- SHOULD NOT BE RUN UNTIL ALL THINGS ARE DEFINED
	-- Handles switches as well
	-- adds table _add to rargs, a cleaned up version of arguments
	-- d : default value
	-- switches : table of switches (in numerical order)
	-- 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

	-- mass dupe removal
	-- this needs to be done at the end to keep dependent parameters working
	-- also removes incompatible data types
	for _, v in ipairs(self.paramnames) do
		-- not removed from dupe enabled params parameters
		if not self.dupeable[v] and self.rargs[v] and self.rargs[v].switches then
			-- tells us whether or not we'll need to remove the switch data
			-- 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

	-- Title parentheses (has to be done here, sadly)
	local _name
	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
		if _name == mw.title.getCurrentTitle().fullText then
			self.rtable:addClass('no-parenthesis-style')
		end
	end	
end

--[[
	Function to link internal use parameters with JS class attribution
	-- self:linkParams{ { arg1, arg2 }, ... { arg1a, arg2a } }
	-- arg1: parameter name being linked
	-- arg2: parameter name that holds the classes
	-- THIS FUNCTION SHOULD BE RUN AFTER :cleanParams()
	-- THIS FUNCTION SHOULD BE RUN BEFORE :finish()
	-- The second argument's data should always contain a value (a CSS class name) at every index
	-- This function is cancelled for non switch boxes
--]]
function Infobox:linkParams(...)
	if not self.switchfo then
		return
	end
	for _, v in ipairs(...) do
		self.switchfoattr[v[1]] = self.rargs[v[2]]
	end
end

--[==========================================[
-- Functions for accessing parameters easily
--]==========================================]
--[[
	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 param = self.rargs[arg]
	-- Return nil if no table found
	if not param then return nil end

	-- Return default
	if ret_func == 'd' then
		return param.d
	end

	-- Return full table
	if ret_func == 'f' then
		return param
	end

	-- Return switch table
	if ret_func == 's' then
		return param.switches
	end

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

--[[
	Checks if a parameter is defined and not blank
	-- arg : parameter to look at
	-- index : index of switches to look at (defaults to default param)
		-- defining 'all' will look at every possible value for the parameter
--]]
function Infobox:paramDefined(arg,index)
	-- Can use cleaned params for switches
	-- but need the passed to identify blanks in the template
	local param = self.rargs[arg]
	local _arg = self.args[arg]
	if string.find(_arg or '','%?action=edit') then
		_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

--[[
	Function to perform a search on all parameters of a defined name
	-- param: param name
	-- val: a value or function
		-- functions passed must return either true or false
		-- 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
	local valtype = type(val)
	-- start with the default value
	-- if it's a function, run it
	if valtype == 'function' then
		ret = val(arg.d)

		-- true means it matched
		if ret == true then
			return ret
		end

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

	-- switch cases
	-- more organised putting them here
	if arg.switches then
		for _, v in ipairs(arg.switches) do
			if valtype == type(v) then
				if valtype == 'string' then
					if val:lower() == v:lower() then
						return true
					end
				elseif val == v then
					return true
				end
			end
		end
	end

	-- return false in every other case
	return false
end
------

function Infobox.paramRead(arg,val)
	-- if no parameters, return nil
	if not arg then
		return nil
	end

	local ret
	local valtype = type(val)
	-- start with the default value
	-- if it's a function, run it
	if valtype == 'function' then
		ret = val(arg.d)

		-- true means it matched
		if ret == true then
			return ret
		end

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

	-- switch cases
	-- more organised putting them here
	if arg.switches then
		for _, v in ipairs(arg.switches) do
			if valtype == type(v) then
				if valtype == 'string' then
					if val:lower() == v:lower() then
						return true
					end
				elseif val == v then
					return true
				end
			end
		end
	end

	-- return false in every other case
	return false
end

----

-- Return collected category data
function Infobox:categoryData()
	return self.catdata
end

-- Infobox:addDropLevelVars("thieving", "skilllvl1")
function Infobox:addDropLevelVars(key, paramName)
	local levelParams = self:param(paramName, 'f')
	local dropParams = self:param('dropversion', 'f')
	if levelParams == nil then
		return
	end
	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'
		else
			dropVersionFromInfobox = dropVersionFromInfobox .. ",DEFAULT"
		end
		for dropVersion in string.gmatch(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
		-- 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 v in string.gmatch(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

-- Override tostring
function Infobox.tostring(box)
	-- If not finished, finish
	if not box.__finished then
		box:finish()
	end

	-- Make entire html wrapper a string and return it
	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

return Infobox
-- </nowiki>