Module:Infobox

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

-- local Infobox = {} Infobox.__index = Infobox Infobox.__tostring = Infobox.tostring

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

-- 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)', 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

-- Standardised chisel links -- expected parameters: (name, link_separator, type1, ids1, ...) -- see docs for more info function makeChiselLinks(...) local ERR_RET = 'ERROR! '	local TYPES = { [0] = { -- defaults id_suffix = '#', id_sep = '@', },		bestiary = { url = 'https://chisel.weirdgloop.org/bestiary/', txt = 'bestiary', id_suffix = '', id_sep = ' ', },		mrnd = { url = 'https://chisel.weirdgloop.org/gazproj/mrnd', txt = 'MRND', },		mrid = { url = 'https://chisel.weirdgloop.org/gazproj/mrid', txt = 'MRID', },		mrod = { url = 'https://chisel.weirdgloop.org/gazproj/mrod', txt = 'MROD', },	}	local name = arg[1] local link_sep = arg[2] if not link_sep or #arg < 4 then mw.log('makeChiselLinks: invalid args, expecting (name, link_separator, type1, ids1, ...)') return ERR_RET end local links = {} local main_type = nil local has_best = false local has_id = false for i = 3, #arg, 2 do		local id_type = TYPES[string.lower(arg[i] or '')] local id_vals = arg[i + 1] local is_best = id_type == TYPES.bestiary local is_main = false

if id_type then has_best = has_best or is_best if not main_type and not is_best then is_main = true main_type = id_type end if Infobox.isDefined(id_vals) then if string.lower(id_vals or '') ~= 'no' then has_id = true id_vals = mw.text.split(id_vals, ', *') local link if #id_vals == 1 then local _id = tonumber(id_vals[1]) if not is_best and _id then link = string.format('[%s?%s#%s-%s %s%s]',								                 id_type.url,								                  _id,								                  math.max(_id - 15, 0),								                  _id + 15,								                  id_type.txt,								                  _id) else link = string.format('[%s%s%s %s]',								                id_type.url,								                 id_type.id_suffix or TYPES[0].id_suffix,								                 id_vals[1],								                 id_type.txt) end else link = string.format('[%s%s%s %s]',							                id_type.url,							                 id_type.id_suffix or TYPES[0].id_suffix,							                 table.concat(id_vals, id_type.id_sep or TYPES[0].id_sep),							                 id_type.txt) end table.insert(links, link) end elseif is_main then -- For main type, must explicitly define it as "no" to hide this search link name = name:gsub(' ', '%%20') table.insert(links, string.format('[%s#%s %s]', main_type.url, name, main_type.txt)) end else mw.log('makeChiselLinks: invalid link type: ' + id_type) end end if not main_type then mw.log('makeChiselLinks: missing a valid type (not bestiary)') return ERR_RET end -- Special case: if "bestiary" requested and no ID given, prepend search link for it as well -- NOTE: name already gsub'd by main type above local ret = '' if not has_id and has_best then ret = string.format('[%s%s %s]%s', TYPES.bestiary.url, name, TYPES.bestiary.txt, link_sep) end ret = ret .. table.concat(links, link_sep) return ret 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, make_chisel_links = makeChiselLinks, makeChiselLinks = makeChiselLinks }

-- 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 = 5 -- 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 tag wrapper	-- other fields are initialized 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,				addrswdbclass = false,				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 rsw-infobox class use before :create noop if not a boolean -- function Infobox:setAddRSWInfoboxClass(bool) if type(bool) == 'boolean' then self.addrswibclass = bool end end

-- Toggles the addition of rsw-databox class use before :create noop if not a boolean If true, removes rsw-infobox class and changes template links cell from td to th -- function Infobox:setAddRSWDataboxClass(bool) if type(bool) == 'boolean' then self.addrswdbclass = bool if bool == true then self.addrswibclass = false end 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('br') 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') :addClass('plainlinks') if self.addrswibclass then self.rtable:addClass('rsw-infobox') end if self.addrswdbclass then self.rtable:addClass('rsw-databox') end -- Add necessary class if switch infobox if self.switchfo then self.rtable:addClass('infobox-switch') end

end

-- Defines an infobox name -- 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 : 		-- td : 		-- argh : 		-- argd : 	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:		 Header args.arg1

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 		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(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(content) _cell:attr('data-attr-param',tostring(v.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('rsw-infobox-switch-buttons-row') :tag('td') :addClass('rsw-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('rsw-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: -- :	-- " " - unqualified and without a number will display the default value -- "" - with a number will show the switch data from that index -- "all " - 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 " ", which will act like "all " - " #" 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 and switchattr.switches then def:attr('data-addclass',switchattr.d)				elseif switchattr then mw.logObject({string.format("Expected switches for `%s` but got none:", v), switchattr}) 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 then _w:attr('data-addclass',switchattr.switches[i]) 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('')

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('') subobj_name = mw.ustring.gsub(subobj_name, '%.', '') end if subobj_name == '0' or subobj_name == '' then self:wikitext('') 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[2],								}), self.infoboxname, self.infoboxname, self.infoboxname, self.infoboxname, self.infoboxname)				) end

bottom_links = table.concat(bottom_links,' &bull; ') links_cell_type = (self.addrswdbclass and 'th') or 'td' self.rtable:tag('tr'):tag(links_cell_type) :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

-- #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 --]==========================================] -- #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 and 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 type(tostring(ret or ''):find('%S')) == 'number' 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 organized 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 organized 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' end for _, dropVersion in ipairs(mw.text.split(dropVersionFromInfobox, ',')) do			if dropVersions[dropVersion] == nil then dropVersions[dropVersion] = {} end dropVersions[dropVersion][levels[i]] = true end end -- This part is to append levels from previous Infobox invocations for dropVersion, dropLevels in pairs(dropVersions) do		-- set dummy property on versioned SMW subobject, otherwise it can't be part of an #ask mw.smw.subobject({["Dummy property"] = true}, dropVersion) -- example variable: DropLevel_combat_High_level local var_name = string.format("DropLevel_%s_%s", key, dropVersion) local previousVar = var.var(var_name) if previousVar ~= "" then for i, v in ipairs(mw.text.split(previousVar, ',')) do				dropVersions[dropVersion][v] = true end end local ordered = {} for k, v in pairs(dropVersions[dropVersion]) do			local n = tonumber(k) if n ~= nil then table.insert(ordered, n)			end end table.sort(ordered) local var_value = table.concat(ordered, ',') var.vardefine(var_name, var_value) mw.log('Set variable `'..var_name..'` with value `'..var_value..'`') 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) .. '' end return tostring(btns) .. tostring(box.rtable) .. table.concat(box.appendStrs, '') .. tostring(box.switch_tag) .. tostring(box.smw_error_tag) end

return Infobox --