Module:Infobox: Difference between revisions

From Brighter Shores Wiki
Jump to navigation Jump to search
Content added Content deleted
(WIP - everything except categories is done, but module is untested)
(When a parameter is nil, make sure the function is called with it being set to nil (previously, all other parameters would be shifted left one))
 
(38 intermediate revisions by 6 users not shown)
Line 4:
 
-- <nowiki>
require('strict')
-- Edit button for unknown params
local editbutton = require('Module:Edit button')
local editsmwutils = editbuttonrequire("'Module:SMW Utils''?''' (edit)")
 
local pagename = mw.title.getCurrentTitle().fullText
 
local Infobox = {}
Line 15 ⟶ 13:
--[[
Infobox class
-- config: table containing configuration parameters
-- infobox_name: the name of the infobox
-- argsparams : parametersdefinitions fromfor frameeach toused pass throughvalue
-- args : values passed from the Template
-- Special args: version, default_version
---- the following values are treated specially: default_version, version, version1, version2...
-- Sets a meta table and creates a <div> tag wrapper
-- other fields are initialized in other functions
--]]
function Infobox.new(argsconfig, configparams, args)
local obj = setmetatable({
args_raw = args, -- parameters (uncleaned)
Line 27 ⟶ 24:
args_smw = {}, -- parameters parsed for smw
infobox_name = nil, -- template name
classes = {}, -- list of classes to add, using the default_version
param_names = {}, -- ordered param list
params = {}, -- param definitions
max_buttons = 6, -- If there are more buttons than the max, the buttons will become a dropdown menu
default_version = 1, -- default version to populate the infobox
versions = nil, -- number of versions
version_names = {'INFOBOX_ERROR'}, -- title of each version (for selection and SMW); version_name[0] should not be used (base index should be 1)
rtable = nil, -- infobox table to return at the end
switch_datatable = nil'', -- datatable for javascript for switch infoboxes
categories = {}, -- set of categories
errors = {}, -- list of errors
__smw_debug = {}, -- debug data for smw subobjects
},
Infobox)
-- Step 1, setup config vars and count the versions
obj:config(config)
obj:parse_versions()
-- Step 2, process the params
obj:create()
obj:define_params(params)
obj:parse_params()
-- Table
obj:table_header()
obj:buttons()
-- Misc
obj:store_smw()
obj:parse_categories()
return obj
end
 
 
--[[
function Infobox:config(...)
Refers to a param after being processed by the validating func
for k, v in pairs(...) do
Used in add_row(), define_params() and is_param_defined()
--]]
function Infobox.param(param_name)
local param = {
property = 'args_parsed',
param_name = param_name,
}
return param
end
 
 
--[[
Refers to a param in raw form as passed from the Template
Used in add_row(), define_params() and is_param_defined()
--]]
function Infobox.raw_param(param_name)
local param = {
property = 'args_raw',
param_name = param_name,
}
return param
end
 
 
--[[
Refers to a param after being processed by the validating smw_func
Used in add_row(), define_params() and is_param_defined()
--]]
function Infobox.smw_param(param_name)
local param = {
property = 'args_smw',
param_name = param_name,
}
return param
end
 
 
--[[
Checks to see if a param is defined.
-- param: Infobox.param(name), Infobox.raw_param(name) or Infobox.smw_param(name)
Returns 0 if param is never defined
1 if param is defined for a fraction of the versions
2 if param is defined for all versions
]]
function Infobox:is_param_defined(param)
local undefined = 0
local defined = 0
for version = 1, self.versions do
local value = self:get_param(param, version)
if value ~= nil then
defined = 1
else
undefined = 1
end
end
return 1 + defined - undefined
end
 
 
--[[
Adds a row to the infobox table
Parameter should be a table of cells, where each cell is a table with the following arguments:
-- tag : 'td' or 'th'
-- content : a string or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
-- attr (optional) : mw.html:attr({ arg1 = '1', ... })
-- css (optional) : mw.html:css({ arg1 = '1', ...)
-- class (optional) : mw.html:addClass('arg')
---- class also supports a table of values, even though mw.html:addClass() does not
---- common classes: infobox-subheader
-- rowspan (optional) : mw.html:attr('rowspan',arg)
-- colspan (optional) : mw.html:attr('colspan',arg)
-- title (optional) : mw.html:attr('title',arg)
 
The row itself may be assigned a single class by setting the value of the key addClass
-- addClass : mw.html:addClass('arg')
-- this function currently only supports a single string
--]]
function Infobox:add_row(...)
local args = ...
local _row = self.rtable:tag('tr')
-- For each cell
for _, v in ipairs(args) do
local _cell = _row:tag(v.tag)
-- Optional parameters
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 v.class then
if type(v.class) == 'string' then
_cell:addClass(v.class)
-- mw.html:addClass() doesn't function with tables, add in a loop
elseif type(v.class) == 'table' then
for _, w in ipairs(v.class) do
_cell:addClass(w)
end
end
end
-- Populate the cell contents
local content = self:get_param(v.content, self.default_version)
if content == nil then
content = self.params[v.content.param_name].empty
end
_cell:wikitext(content)
-- Add the switch data if multiple values exist
local data_attr_param = self:add_switch_data(v.content)
if data_attr_param then
_cell:attr('data-attr-param', data_attr_param)
end
end
-- allow classes to be defined on the whole row
if args.addClass then
_row:addClass(args.addClass)
end
return self
end
 
 
--[[
Adds a blank row of padding spanning the given number of columns
--]]
function Infobox:pad(colspan, class)
local tr = self.rtable:tag('tr'):tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding')
if class then
tr:addClass(class)
end
return self
end
 
--[[
Adds a class to the table as a whole
--]]
function Infobox:addClass(arg)
self.rtable:addClass(arg)
return self
end
 
 
--[[
Setup config values
-- config: table containing configuration parameters
---- infobox_name = mandatory unique identifier for this infobox, used for css
---- max_buttons = max number of switch buttons before using a dropdown list instead
--]]
function Infobox:config(config)
for k, v in pairs(config) do
if k == 'infobox_name' then
self.infobox_name = mw.ustring.gsub(v, '%s', '_')
elseif k == 'max_buttons' then
self.max_buttons = tonumber(v)
elseif k == 'class' then
if type(v) == 'string' then
self.classes = {v}
elseif type(v) == 'table' then
self.classes = v
end
end
end
if self.infobox_name == nil then
table.insert(self.errors, 'infobox_name needs to be defined in Infobox.new()\'s config!')
end
return self
end
 
 
--[[
Counts the number of versions in the infobox
Function for defining parameters
Populates Infobox.version_names
-- name : parameter name
Sets Infobox.default_version
-- 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:parse_versions()
local function insert_version_name(version_name)
table.insert(self.version_names, version_name)
if smwutils.valid_subobject_name(version_name) == false then
table.insert(self.errors, 'Illegal version value: must not be "0" nor contain a "." in the first five characters')
end
end
-- Count the versions and setup self.version_names
local i = 1
while self.args_raw['version'..i] do
insert_version_name(self.args_raw['version'..i])
i = i + 1
end
self.versions = i - 1
-- Should either have 0 or 2+ versions
if self.versions == 1 then
table.insert(self.errors, 'There should be multiple versions or no versions. If defining a custom version name for a single entry, use "version=Name" instead of "version1=Name".')
end
-- Handle the no version case - check for a custom version_name
if self.versions == 0 then
insert_version_name(self.args_raw['version'] or 'DEFAULT')
self.versions = 1
end
-- Check for a default_version
if self.args_raw['default_version'] then
self.default_version = tonumber(self.args_raw['default_version'])
if self.default_version > self.versions then -- Make sure the default version exists
self.default_version = 1
end
end
end
 
 
--[[
Define all used parameters (except for default_version/version)
-- name : parameter name as used in the Template
-- func : function to validate and process the Template argument
-- If func is a function, will call func(Infobox.raw_param(name))
-- If func is a table, contains the following key-value pairs:
---- name : function
---- params : list of arguments to pass to the function (use Infobox.raw_param(),
---- Infobox.param() and Infobox.smw_param() to use arguments)
-- empty (optional) : text to display in the infobox if func returns nil, defaults to "? (edit)"
-- category_never : category to add if func returns nil for all versions
-- category_partial : category to add if func returns nil for some versions, but a value for other versions
-- category_incomplete : category to add if func returns nil for at least 1 version (i.e. category_never and category_partial combined)
-- category_complete : category to add if func returns a value for all versions
-- smw_property (optional) : if this string is defined, the parameter will be saved into smw
-- smw_func (optional) : function to validate and process the Template argument to save into smw
-- func is used by default if smw_func is not defined
--]]
function Infobox:define_params(...)
-- For every parameter, store its corresponding function to self.params
for _, v in ipairs(...) do
-- For every parameter, store its corresponding function to self.params
if v.name then
local param = {}
Line 86 ⟶ 307:
end
end
-- If empty is not defined, default message is "? (edit)"
param.empty = v.empty or editbutton("'''?''' (edit)")
-- Get the category names
param.category_never = v.category_never
param.category_partial = v.category_partial
param.category_incomplete = v.category_incomplete
param.category_complete = v.category_complete
-- Store the param
self.params[v.name] = param
table.insert(self.param_names, v.name)
end
end
self:parse_params()
--self:parse_categories()
return self
end
 
 
--[[
Fetches a param value. If the value is nil, will return the default value instead
Counts the number of versions in the infobox, and populates version_names
-- arg: a non-table value (string, number, etc), or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
-- version: 0/'' for default, or else a number
Returns arg if a constant
param value (or default value) if Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
--]]
function Infobox:parse_versionsget_param(arg, version)
if version == 0 then
-- Count the versions
local i version = 1''
while self.args_raw['version'..i] do
table.insert(self.version_names, self.args_raw['version'..i])
i = i + 1
end
-- Handle Infobox.param(), Infobox.raw_param(), Infobox.smw_param()
self.versions = i - 1
if type(arg) == 'table' then
-- Handle the no version case - might have a custom version_name
local value = self[arg.property][arg.param_name..version]
if self.versions == 0 then
-- If nil, grab default value (which could be nil as well)
table.insert(self.version_names, self.args_raw['version'] or 'DEFAULT')
if value == nil then
value = self[arg.property][arg.param_name]
end
return value
end
-- Everything else passes through unmodified (string, number, etc)
if self.versions == 1 then
return arg
table.insert(self.errors, 'There should be multiple versions or no versions. If defining a custom version name for a single entry, use "version=Name" instead of "version1=Name".')
self.versions = 0
end
if self.args_raw['default_version'] then
self.default_version = tonumber(self.args_raw['default_version'])
if self.default_version > self.versions then -- Make sure the default version exists
self.default_version = 1
end
end
end
 
 
-- Value is not defined if "action=edit" exists in string, otherwise
--[[
-- value is defined if not nil and non-empty string
Calculates the parsed value of a param
function Infobox.is_value_defined(value)
-- param_name : param_name as a string
if value == nil then
-- version : 0/'' for default, or else a number
return false
-- smw : boolean, will use smw_func if true, or func is false
--]]
function Infobox:parse_param(param_name, version, smw)
if version == 0 then
version = ''
end
-- use func or smw_func depending on smw argument
if type(value) ~= 'string' then
local param = self.params[param_name]
return true
local func = smw and param.smw_func or param.func
-- call functions by passing the param_name
if type(func) == 'function' then
return func(self:get_param(self.raw_param(param_name), version))
-- call tables by grabbing the function and reading the param arguments
elseif type(func) == 'table' then
local func_name = func.name
local func_params = func.params
local func_fetched_params = {}
local i = 1
for _, func_param in ipairs(func_params) do
func_fetched_params[i] = self:get_param(func_param, version)
i = i + 1
end
return func_name(unpack(func_fetched_params))
else
table.insert(self.errors, 'Invalid param definition for '..param_name)
end
if value:find('action=edit') then
return false
end
if value:find('%S') then
return true
end
return false
end
 
 
--[[
Parses all the param values (args_parsed and args_smw)
Checks to see if a param is defined.
--]]
Returns 0 if param is never defined. Returns 1 if param is defined for some versions. Returns 2 if param is defined for all versions.
function Infobox:parse_params()
-- param: a table generated from Infobox:param(), Infobox:raw_param() or Infobox:smw_param()
-- Calculate the param value for all params and all versions
]]
for _, param_name in ipairs(self.param_names) do
function Infobox:is_param_defined(param)
for version=0, self.versions do
local undefined = 0
local if definedversion == 0 then
for version = 1, self.versions do''
end
local value = self:get_param(param, version)
-- get the parsed value
if self.is_value_defined(value) then
self.args_parsed[param_name..version] = self:parse_param(param_name, version, false)
defined = 1
-- Only get the smw value if smw_property is defined
else
if self.params[param_name].smw_property then
undefined = 1
self.args_smw[param_name..version] = self:parse_param(param_name, version, true)
end
end
end
return 1 + defined - undefined
end
 
 
--[[
Creates the table header
Fetches a param value. If the value is nil, will return the default value instead
-- arg: a table generated from Infobox:param(), Infobox:raw_param() or Infobox:smw_param(), or else
-- version: '' for default, or else a number
--]]
function Infobox:get_paramtable_header(arg, version)
-- Create infobox table
if version == 0 then
self.rtable = mw.html.create('table')
version = ''
:addClass('plainlinks')
end
:addClass('infobox')
if type(arg) == 'table' then
:addClass('infobox-'..self.infobox_name)
local value = self[arg.property][arg.param_name..version]
for _, class in ipairs(self.classes) do
if value == nil then -- Try to get default value if it exists
local class_name = self:get_param(class, self.default_version)
value = self[arg.property][arg.param_name]
if type(class_name) == 'string' then
end
self.rtable:addClass('infobox-'..mw.ustring.gsub(class_name, '%s', '_'))
return value
end
end
-- Other (int, string)
-- Create the switch datatable if multiple versions
return arg
if self.versions > 1 then
self.rtable:addClass('infobox-switch')
self.switch_datatable = mw.html.create('div')
:addClass('infobox-switch-resources')
:addClass('infobox-resources-'..self.infobox_name)
:addClass('hidden')
end
return self
end
 
 
--[[
If multiple versions exist, creates a set of buttons for switching
Calculates the parsed value of a param
-- self.max_buttons : if the number of versions exceeds this value, a dropdown will be created instead
-- param_name : string, name of the param
-- version : 0/'' for default, or else a number
-- smw : boolean, whether to use the smw function or default function
--]]
function Infobox:parse_parambuttons(param_name, version, smw)
if versionself.versions ==< 02 then
return
version = ''
end
-- Button caption
-- use func or smw_func depending on smw argument
local parambuttons = self.params[param_name]rtable:tag('caption')
:tag('div')
local func = smw and param.smw_func or param.func
:addClass('infobox-buttons')
-- call functions by passing the param_name
:attr('data-default-index', self.default_version)
if type(func) == 'function' then
-- Dropdown list instead of buttons if too many versions
return func(self:get_param(self.raw_param(param_name), version))
if self.versions > self.max_buttons then
-- call tables by grabbing the function and reading the param arguments
buttons:addClass('infobox-buttons-select')
elseif type(func) == 'table' then
end
local func_name = func.name
-- Create all the buttons
local func_params = func.params
for version=1, self.versions do
local func_fetched_params = {}
local button = buttons:tag('span')
for _, func_param in ipairs(func_params) do
:attr('data-switch-index', version)
table.insert(func_fetched_params, self:get_param(func_param, version))
:attr('data-switch-anchor', '#'..self.version_names[version])
end
:addClass('button')
return func_name(unpack(func_fetched_params))
:wikitext(self.version_names[version])
else
-- In case of dropdown list, hide the buttons as the switch gadget will convert them to a dropdown list - we can't directly create the dropdown list here as the select/option tags are rejected by wikitext
table.insert(self.errors, 'Invalid param definition for '..param_name)
if self.versions > self.max_buttons then
end
button:addClass('hidden')
end
end
 
end
 
function Infobox:parse_params()
-- Calculate the param value for all params and all versions
for _, param_name in ipairs(self.param_names) do
for version=0, self.versions do
if version == 0 then
version = '' -- default version
end
-- Only get the parsed value if the raw value is defined
if self.args_raw[param_name..version] then
self.args_parsed[param_name..version] = self:parse_param(param_name, version, false)
-- Only get the smw value if smw_property is defined
if self.params[param_name].smw_property then
self.args_smw[param_name..version] = self:parse_param(param_name, version, true)
end
end
end
end
end
 
 
--[[
For each version, stores a subobject for all params that have smw_property defined
--]]
function Infobox:store_smw()
-- Abort if not in mainspace
if mw.title.getCurrentTitle().namespace ~= 0 then
return false
end
-- Store a subobject for each version
for version=1, self.versions do
-- Generate a subobject name
-- Reminder - subobject name cannot have a . in the first 5 characters
local subobject_name = 'Infobox.'..pagename..'#'..self.version_names[version]
-- Store each param that has smw_property defined and is not nil
local subobject = {}
local subobject = {Infobox = self.infobox_name} -- Also store the current Infobox name
-- Store each param that has smw_property defined and has a defined value
for _, param_name in ipairs(self.param_names) do
local property = self.params[param_name].smw_property
if property then
local value = self:get_param(self.smw_param(param_name), version)
if self.is_value_defined(value) ~= nil then
subobject[property] = value
end
end
end
-- Keep a copy for debugging
local result = mw.smw.subobject(subobject, subobject_name)
self.__smw_debug[subobject_name] = subobject
if result ~= true then
-- Save subobjects if not in mainspace
table.insert(self.errors, 'SMW error: '..result.error)
if mw.title.getCurrentTitle():inNamespace(0) then
local result = true
if self.versions == 1 then
result = mw.smw.set(subobject)
else
result = mw.smw.subobject(subobject, subobject_name)
end
if result ~= true then
table.insert(self.errors, 'SMW error: '..result.error)
end
end
end
return true
end
 
function Infobox.param(param_name)
param = {
property = 'args_parsed',
param_name = param_name,
}
return param
end
 
function Infobox.raw_param(param_name)
param = {
property = 'args_raw',
param_name = param_name,
}
return param
end
 
function Infobox.smw_param(param_name)
param = {
property = 'args_smw',
param_name = param_name,
}
return param
end
 
-----------
-- Table --
-----------
function Infobox:buttons()
if self.versions < 2 then
return
end
local buttons = self.rtable:tag('caption')
:tag('div')
:addClass('infobox-buttons')
:attr('data-default-version', self.default_version)
-- Dropdown list instead of buttons
if self.versions > self.max_buttons then
buttons:addClass('infobox-buttons-select')
end
for version=1, self.versions do
buttons:tag('span')
:attr('data-switch-index', version)
:attr('data-switch-anchor', '#'..self.version_names[version])
:addClass('button')
:wikitext(self.version_names[version])
end
end
 
function Infobox:create()
-- Create infobox table
self.rtable = mw.html.create('table')
:addClass('plainlinks')
:addClass('infobox')
:addClass('infobox-'..self.infobox_name)
-- Add necessary class if switch infobox
if self.versions > 1 then
self.rtable:addClass('infobox-switch')
self.switch_datatable = mw.html.create('div')
:addClass('infobox-switch-resources')
:addClass('infobox-resources-'..self.infobox_name)
:addClass('hidden')
self.switch_datatable:tag('span'):wikitext('Versions: '..self.versions)
self.switch_datatable:tag('span'):wikitext('Default version: '..self.default_version)
self:buttons()
end
return self
end
 
 
function Infobox:add_switch_data(content)
-- Only check for params if there are multiple versions
if self.versions <= 1 then
return false
end
if type(content) ~= 'table' then
return false
end
-- Only if the data varies between the different versions
local first_value = self:get_param(cell_params.content, 1)
local all_same = true
for version=2, self.versions do
if first_value ~= self:get_param(cell_params.content, version) then
all_same = false
break
end
end
if all_same then
return false
end
-- Let's build the datatable
-- Prepend raw__ or smw$__ if not a parsed argument
local name = content.param_name
if content.property == 'args_raw' then
name = 'raw__' + name
end
if content.property == 'args_smw' then
name = 'smw__' + name
end
data_param = self.switch_datatable:tag('span'):attr('data-attr-param', name)
-- Add each version to the datatable
for version=1, self.versions do
text = self:get_param(content, version)
if text == nil then
text = edit
end
data_param:tag('span'):attr('data-attr-index', version):wikitext(text)
end
-- return the 'data-attr-param' name
return name
end
 
--[[
Automatically add categories for each param as defined in define_params()
Add parameters functions
All parameters should be tables
The first parameter defines the type of cell to create
-- tag = 'th' or 'td'
The second parameter defines what is inside the tag
-- content = string or Infobox.param
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') (common class: infobox-subheader)
---- 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 "addClass'
-- addClass : mw.html:addClass('arg')
-- this function currently only supports a single string
--]]
function Infobox:add_rowparse_categories(...)
for _, param_name in ipairs(self.param_names) do
-- New row to add
local argsparam = self...params[param_name]
local _rowdefined = self.rtable:tagis_param_defined(Infobox.param('tr'param_name))
if defined == 0 and param.category_never then
-- For each member of tags
self.categories[param.category_never] = 1
for _, v in ipairs(args) do
local _cell = _row:tag(v.tag)
 
-- 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 vdefined == 1 and param.colspancategory_partial then
self.categories[param.category_partial] = 1
_cell:attr('colspan',v.colspan)
end
if vdefined < 2 and param.rowspancategory_incomplete then
self.categories[param.category_incomplete] = 1
_cell:attr('rowspan',v.rowspan)
end
if vdefined == 2 and param.titlecategory_complete then
self.categories[param.category_complete] = 1
_cell:attr('title',v.title)
end
if v.css then
_cell:css(v.css)
end
end
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
 
--[[
local content = Infobox:get_param(v.content, self.default_version)
When a parameter is added into the table, add the alternative versions' data into the switch datatable
if content == nil then
-- content : a non-table value (string, number, etc), or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
content = edit
Returns the name of the parameter in the tag 'data-attr-param'
end
--]]
_cell:wikitext(content)
function Infobox:add_switch_data(content)
 
if self.versions < 2 then
-- Add the switch data if multiple values exist
return false
data_attr_param = self:add_switch_data(v.content)
if data_attr_param then
_cell:attr('data-attr-param', data_attr_param)
end
end
-- Only needed for non-constant params
 
if type(content) ~= 'table' then
-- allow classes to be defined on the whole row
return false
if args.addClass then
_row:addClass(args.addClass)
end
-- Only needed if the param changes value between different versions
 
local first_value = self:get_param(content, 1)
return self
local all_same = true
for version=2, self.versions do
if first_value ~= self:get_param(content, version) then
all_same = false
break
end
end
if all_same then
return false
end
-- Let's build the datatable
-- datum name: Prepend raw__ or smw__ if not a parsed argument
local name = content.param_name
if content.property == 'args_raw' then
name = 'raw__' + name
end
if content.property == 'args_smw' then
name = 'smw__' + name
end
local data_param = self.switch_datatable:tag('span'):attr('data-attr-param', name)
-- Add each version to the datatable
for version=1, self.versions do
local text = self:get_param(content, version)
if text == nil then
text = self.params[content.param_name].empty
end
data_param:tag('span'):attr('data-attr-index', version):wikitext(text)
end
-- return the 'data-attr-param' name
return name
end
 
 
--[[
Override tostring
-- adds a blank row of padding spanning the given number of columns
--]]
function Infobox:pad__tostring(colspan, class)
-- Create error messages
local tr = self:tag('tr')
local error_text = ''
:tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding')
for _, v in ipairs(self.errors) do
if class then
error_text = error_text..'<span class="mw-message-box-error">'..v..'</span>'
tr:addClass(class)
end
-- Create categories
return self
local category_text = ''
if mw.title.getCurrentTitle():inNamespace(0) then
for key, _ in pairs(self.categories) do
category_text = category_text..'[[Category:'..key..']]'
end
end
local dump = ''
if self.args_raw.__dump then
setmetatable(self, nil)
dump = '<nowiki>'..mw.dumpObject(self)..'</nowiki>'
setmetatable(self, Infobox)
end
return tostring(self.rtable) .. tostring(self.switch_datatable) .. error_text .. category_text .. dump
end
 
-- addClass
function Infobox:addClass(arg)
self.rtable:addClass(arg)
return self
end
 
-- Override tostring
function Infobox:tostring()
error_text = ''
if #self.errors > 0 then
for _, v in ipairs(self.errors) do
error_text = error_text..'<span class="mw-message-box-error">'..v..'</span>'
end
end
return tostring(self.rtable) .. tostring(self.switch_datatable) .. tostring(error_text)
end
 
 
 
--[[
Debug function
--]]
function Infobox:dump()
-- Temporarily detach the metatable so we don't override tostring anymore, allowing us to use dumpObject
setmetatable(self, nil)
mw.log(mw.dumpObject(self))
setmetatable(self, Infobox)
mw.log(tostring(self))
end
 
 
return Infobox

Latest revision as of 12:28, 26 November 2024

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

Creating a template step-by-step

Import Module:Infobox and Module:Param Parse

local Infobox = require('Module:Infobox')
local parse = require('Module:Param Parse')

Unpack the frame arguments from the Template

local p = {}

function p.main(frame)
	local args = frame:getParent().args
	...

Setup the Module config settings

	local config = {
		infobox_name = 'Scenery',
		class = {Infobox.smw_param('episode')}, -- Add css class with episode name to colorize Infobox
	}

Map your arguments to parsing functions

Use the params in Module:Param Parse to validate and format the data - feel free to create your own new params in Module:Param Parse.

	local params = {
    	parse.name,
        parse.image,
		{name = 'description', func = parse.has_content, smw_property = 'Description'}, -- Custom param
        parse.episode,
 		...
	}

Define an Infobox object

	local infobox = Infobox.new(config, params, args)

Create your table

	infobox
		:add_row{
			{tag='th', content=Infobox.param('name'), class='infobox-header', colspan='20'},
		}
		:add_row{
			{tag='td', content=Infobox.param('image'), class='infobox-image', colspan='20'},
		}
		:pad("20")
		:add_row{
			{tag='td', content='Info', class='infobox-subheader', colspan='20'},
		}
		:pad("20")
		:add_row{
			{tag='th', content='Description', colspan="6"},
			{tag='td', content=Infobox.param('description'), colspan="14"},
		}
		:add_row{
			{tag='th', content='[[Episode]]', colspan="6"},
			{tag='td', content=Infobox.param('episode'), colspan="14"},
		}
		...


You're done!

	return infobox
end

return p

Functions

Special params

You don't need to do anything about these special parameters, but they may be used as parameters within the Template:

param explanation
version1, version2, version3 Button label and SMW name for each switch version of the infobox
default_version The default version to display when the page is loaded
version If there is only a single version, you can use version to set the SMW name (default SMW name is "DEFAULT")

Referring to params

Each parameter can have a different value for each version. In addition, there are 3 different representations of each value. Therefore, a parameter must be accessed via one of the 3 helper functions:

helper function example explanation
Infobox.raw_param(name) "1000" Raw value as passed by the Template
Infobox.param(name) "1,000" Value formatted for display in the Infobox
Infobox.smw_param(name) 1000 Value formatted to be saved as an SMW property

Infobox.new(config, params, args)

Creates a new infobox. Automatically parses the arguments, creates SMW subobjects and adds categories.

	local infobox = Infobox.new(config, params, args)

config

There are only 3 parameters for config

	local config = {
    	infobox_name = 'Scenery', -- mandatory unique identifier for css
        class = {'CustomClass', Infobox.smw_param('episode')} -- optional, defaults to {}. Adds css classes to infobox table: {'infobox-CustomClass', 'infobox-[default version parameter's value]'}
    	max_buttons = 6, -- optional, defaults to 6, max number of switch buttons before using a dropdown list instead
	}

params

A list of parameters to be processed by the Infobox module

	local params = {
		{ name = <param>, func = <func>, ... },
		...
	}


key value
name parameter name as used in the Template
func A function in Module:Param Parse to validate and process the Template argument. You can also use a local function, but this is not recommended.

If func is a function, will call func(Infobox.raw_param(name)):

	{name = <param>, func = <func>, ... },

If func is a table, it takes the following parameters:

	{name = <param>, func = { name = <func>, params = <func_params>}, ... },
name function in Module:Param Parse
params a list of parameters to pass the the function, in the form of constants, or Infobox.raw_param(name), Infobox.param(name), Infobox.smw_param(name)
empty (optional) text to display in the infobox if func returns nil; defaults to "? (edit)"
category_never (optional) category to add if func returns nil for all versions
category_partial (optional) category to add if func returns nil for some versions, but a value for other versions
category_incomplete (optional) category to add if func returns nil for at least 1 version (i.e. category_never and category_partial combined)
category_complete (optional) category to add if func returns a value for all versions
smw_property (optional) if this string is defined, the parameter will be saved into SMW
smw_func (optional) function to validate and process the Template argument to save into SMW. func is used by default if smw_func is not defined

args

Arguments passed via the Template

	local args = frame:getParent().args

infobox:is_param_defined(param)

Used to conditionally display a line in the infobox

	infobox:add_row{
		{tag='th', content=Infobox.param('name'), class='infobox.subheader', colspan='2'},
	}
	if infobox:is_param_defined(Infobox.param('owner')) > 0 then
		infobox:add_row{
			{tag='td', content='[[Owner]]'},
			{tag='td', content=Infobox.param('owner')},
		}
	end
param a parameter referenced via Infobox.raw_param(name), Infobox.param(name) or Infobox.smw_param(name)
Returns 0 if param is never defined

1 if param is defined for a fraction of the versions

2 if param is defined for all versions

Infobox:add_row(...)

Adds a row to the infobox table. Parameter should be a table of cells:

	infobox:add_row{
		{tag='td', content='[[Cell1]]', ...},
		{tag='td', content=Infobox.param('param'), ...},
		{tag='td', content='[[Cell3]]', ...},
        ...
        addClass = 'row-class'
	}

Each cell should have a set of key-values, of which only the tag and content are mandatory:

key value
tag 'td' or 'th'
content a string or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
attr (optional) a table of attributes to add to the cell

mw.html:attr({ arg1 = '1', ... })

css (optional) a table of css to add to the cell

mw.html:css({ arg1 = '1', ... })

class (optional) a string with a class to add, or a table of classes to add. infobox-header, infobox-subheader and infobox-image are commonly used.

mw.html:addClass(...)

rowspan (optional) Set the cell rowspan

mw.html:attr('rowspan',arg)

rowspan (optional) Set the cell colspan

mw.html:attr('colspan',arg)

title (optional) Set the cell title

mw.html:attr('title',arg)


Infobox:pad(colspan, class)

Adds a blank row of padding spanning the given number of columns. Will always add the class infobox-padding, but can optionally add another class:

	infobox:pad("10", class=<class>)


Infobox:addClass(class)

Adds a class to the entire table

	infobox:addClass(class)


Infobox:dump()

Logs all the values into the Debug console for debugging purposes. You can also dump all the values in an Infobox template by setting a template parameter "__dump = Yes".


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

-- <nowiki>
require('strict')
local editbutton = require('Module:Edit button')
local smwutils = require('Module:SMW Utils')

local Infobox = {}
Infobox.__index = Infobox

--[[
	Infobox class
	-- config: table containing configuration parameters
	-- params : definitions for each used value
	-- args : values passed from the Template
	---- the following values are treated specially: default_version, version, version1, version2...
--]]
function Infobox.new(config, params, args)
	local obj = setmetatable({
				args_raw = args, -- parameters (uncleaned)
				args_parsed = {}, -- parsed parameters
				args_smw = {}, -- parameters parsed for smw
				infobox_name = nil, -- template name
				classes = {}, -- list of classes to add, using the default_version
				param_names = {}, -- ordered param list
				params = {}, -- param definitions
				max_buttons = 6, -- If there are more buttons than the max, the buttons will become a dropdown menu
				default_version = 1, -- default version to populate the infobox
				versions = nil, -- number of versions
				version_names = {}, -- title of each version (for selection and SMW)
				rtable = nil, -- infobox table to return at the end
				switch_datatable = '', -- datatable for javascript for switch infoboxes
				categories = {}, -- set of categories
				errors = {}, -- list of errors
				__smw_debug = {}, -- debug data for smw subobjects
				},
			Infobox)
	-- Step 1, setup config vars and count the versions
	obj:config(config)
	obj:parse_versions()
	-- Step 2, process the params
	obj:define_params(params)
	obj:parse_params()
	-- Table
	obj:table_header()
	obj:buttons()
	-- Misc
	obj:store_smw()
	obj:parse_categories()
	return obj
end


--[[
	Refers to a param after being processed by the validating func
	Used in add_row(), define_params() and is_param_defined()
--]]
function Infobox.param(param_name)
	local param = {
		property = 'args_parsed',
		param_name = param_name,
	}
	return param
end


--[[
	Refers to a param in raw form as passed from the Template
	Used in add_row(), define_params() and is_param_defined()
--]]
function Infobox.raw_param(param_name)
	local param = {
		property = 'args_raw',
		param_name = param_name,
	}
	return param
end


--[[
	Refers to a param after being processed by the validating smw_func
	Used in add_row(), define_params() and is_param_defined()
--]]
function Infobox.smw_param(param_name)
	local param = {
		property = 'args_smw',
		param_name = param_name,
	}
	return param
end


--[[
	Checks to see if a param is defined.
	-- param: Infobox.param(name), Infobox.raw_param(name) or Infobox.smw_param(name)
	Returns 0 if param is never defined
	        1 if param is defined for a fraction of the versions
			2 if param is defined for all versions
]]
function Infobox:is_param_defined(param)
	local undefined = 0
	local defined = 0
	for version = 1, self.versions do
		local value = self:get_param(param, version)
		if value ~= nil then
			defined = 1
		else
			undefined = 1
		end
	end
	return 1 + defined - undefined
end


--[[
	Adds a row to the infobox table
	Parameter should be a table of cells, where each cell is a table with the following arguments:
	-- tag : 'td' or 'th'
	-- content : a string or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
	-- attr (optional) : mw.html:attr({ arg1 = '1', ... })
	-- css (optional) : mw.html:css({ arg1 = '1', ...)
	-- class (optional) : mw.html:addClass('arg')
	---- class also supports a table of values, even though mw.html:addClass() does not
	---- common classes: infobox-subheader
	-- rowspan (optional) : mw.html:attr('rowspan',arg)
	-- colspan (optional) : mw.html:attr('colspan',arg)
	-- title (optional) : mw.html:attr('title',arg)

	The row itself may be assigned a single class by setting the value of the key addClass
		-- addClass : mw.html:addClass('arg')
		-- this function currently only supports a single string
--]]
function Infobox:add_row(...)
	local args = ...
	local _row = self.rtable:tag('tr')
	-- For each cell
	for _, v in ipairs(args) do
		local _cell = _row:tag(v.tag)
		-- Optional parameters
		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 v.class then
			if type(v.class) == 'string' then
				_cell:addClass(v.class)
			-- mw.html:addClass() doesn't function with tables, add in a loop
			elseif type(v.class) == 'table' then
				for _, w in ipairs(v.class) do
					_cell:addClass(w)
				end
			end
		end
		-- Populate the cell contents
		local content = self:get_param(v.content, self.default_version)
		if content == nil then
			content = self.params[v.content.param_name].empty
		end
		_cell:wikitext(content)
		-- Add the switch data if multiple values exist
		local data_attr_param = self:add_switch_data(v.content)
		if data_attr_param then
			_cell:attr('data-attr-param', data_attr_param)
		end
	end
	-- allow classes to be defined on the whole row
	if args.addClass then
		_row:addClass(args.addClass)
	end
	return self
end


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

--[[
	Adds a class to the table as a whole
--]]
function Infobox:addClass(arg)
	self.rtable:addClass(arg)
	return self
end


--[[
	Setup config values
	-- config: table containing configuration parameters
	---- infobox_name = mandatory unique identifier for this infobox, used for css
	---- max_buttons = max number of switch buttons before using a dropdown list instead
--]]
function Infobox:config(config)
	for k, v in pairs(config) do
		if k == 'infobox_name' then
			self.infobox_name = mw.ustring.gsub(v, '%s', '_')
		elseif k == 'max_buttons' then
			self.max_buttons = tonumber(v)
		elseif k == 'class' then
			if type(v) == 'string' then
				self.classes = {v}
			elseif type(v) == 'table' then
				self.classes = v
			end
		end
	end
	if self.infobox_name == nil then
		table.insert(self.errors, 'infobox_name needs to be defined in Infobox.new()\'s config!')
	end
	return self
end


--[[
	Counts the number of versions in the infobox
	Populates Infobox.version_names
	Sets Infobox.default_version
--]]
function Infobox:parse_versions()
	local function insert_version_name(version_name)
		table.insert(self.version_names, version_name)
		if smwutils.valid_subobject_name(version_name) == false then
			table.insert(self.errors, 'Illegal version value: must not be "0" nor contain a "." in the first five characters')
		end
	end
		
	-- Count the versions and setup self.version_names
	local i = 1
	while self.args_raw['version'..i] do
		insert_version_name(self.args_raw['version'..i])
		i = i + 1
	end
	self.versions = i - 1
	-- Should either have 0 or 2+ versions
	if self.versions == 1 then
		table.insert(self.errors, 'There should be multiple versions or no versions. If defining a custom version name for a single entry, use "version=Name" instead of "version1=Name".')
	end
	-- Handle the no version case - check for a custom version_name
	if self.versions == 0 then
		insert_version_name(self.args_raw['version'] or 'DEFAULT')
		self.versions = 1
	end
	-- Check for a default_version
	if self.args_raw['default_version'] then
		self.default_version = tonumber(self.args_raw['default_version'])
		if self.default_version > self.versions then -- Make sure the default version exists
			self.default_version = 1
		end
	end
end


--[[
	Define all used parameters (except for default_version/version)
	-- name : parameter name as used in the Template
	-- func : function to validate and process the Template argument
	--        If func is a function, will call func(Infobox.raw_param(name))
	--        If func is a table, contains the following key-value pairs:
	----          name : function
	----          params : list of arguments to pass to the function (use Infobox.raw_param(),
	----                   Infobox.param() and Infobox.smw_param() to use arguments)
	-- empty (optional) : text to display in the infobox if func returns nil, defaults to "? (edit)"
	-- category_never : category to add if func returns nil for all versions
	-- category_partial : category to add if func returns nil for some versions, but a value for other versions
	-- category_incomplete : category to add if func returns nil for at least 1 version (i.e. category_never and category_partial combined)
	-- category_complete : category to add if func returns a value for all versions
	-- smw_property (optional) : if this string is defined, the parameter will be saved into smw
	-- smw_func (optional) : function to validate and process the Template argument to save into smw
	--                      func is used by default if smw_func is not defined
--]]
function Infobox:define_params(...)
	-- For every parameter, store its corresponding function to self.params
	for _, v in ipairs(...) do
		if v.name then
			local param = {}
			-- Copy the function
			if type(v.func) == 'function' or type(v.func) == 'table' then
				param.func = v.func
			end
			-- If smw_property is defined, then use smw_func, or default to func if it is not defined
			if v.smw_property then
				param.smw_property = v.smw_property
				if type(v.smw_func) == 'function' or type(v.smw_func) == 'table' then
					param.smw_func = v.smw_func
				else
					param.smw_func = param.func
				end
			end
			-- If empty is not defined, default message is "? (edit)"
			param.empty = v.empty or editbutton("'''?''' (edit)")
			-- Get the category names
			param.category_never = v.category_never
			param.category_partial = v.category_partial
			param.category_incomplete = v.category_incomplete
			param.category_complete = v.category_complete
			-- Store the param
			self.params[v.name] = param
			table.insert(self.param_names, v.name)
		end
	end
	return self
end


--[[
	Fetches a param value. If the value is nil, will return the default value instead
	-- arg: a non-table value (string, number, etc), or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
	-- version: 0/'' for default, or else a number
	Returns arg if a constant
	        param value (or default value) if Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
--]]
function Infobox:get_param(arg, version)
	if version == 0 then
		version = ''
	end
	-- Handle Infobox.param(), Infobox.raw_param(), Infobox.smw_param()
	if type(arg) == 'table' then
		local value = self[arg.property][arg.param_name..version]
		-- If nil, grab default value (which could be nil as well)
		if value == nil then
			value = self[arg.property][arg.param_name]
		end
		return value
	end
	-- Everything else passes through unmodified (string, number, etc)
	return arg
end


--[[
	Calculates the parsed value of a param
	-- param_name : param_name as a string
	-- version : 0/'' for default, or else a number
	-- smw : boolean, will use smw_func if true, or func is false
--]]
function Infobox:parse_param(param_name, version, smw)
	if version == 0 then
		version = ''
	end
	-- use func or smw_func depending on smw argument
	local param = self.params[param_name]
	local func = smw and param.smw_func or param.func
	-- call functions by passing the param_name
	if type(func) == 'function' then
		return func(self:get_param(self.raw_param(param_name), version))
	-- call tables by grabbing the function and reading the param arguments
	elseif type(func) == 'table' then
		local func_name = func.name
		local func_params = func.params
		local func_fetched_params = {}
		local i = 1
		for _, func_param in ipairs(func_params) do
			func_fetched_params[i] = self:get_param(func_param, version)
			i = i + 1
		end
		return func_name(unpack(func_fetched_params))
	else
		table.insert(self.errors, 'Invalid param definition for '..param_name)
	end
end


--[[
	Parses all the param values (args_parsed and args_smw)
--]]
function Infobox:parse_params()
	-- Calculate the param value for all params and all versions
	for _, param_name in ipairs(self.param_names) do
		for version=0, self.versions do
			if version == 0 then
				version = ''
			end
			-- get the parsed value
			self.args_parsed[param_name..version] = self:parse_param(param_name, version, false)
			-- Only get the smw value if smw_property is defined
			if self.params[param_name].smw_property then
				self.args_smw[param_name..version] = self:parse_param(param_name, version, true)
			end
		end
	end
end


--[[
	Creates the table header
--]]
function Infobox:table_header()
	-- Create infobox table
	self.rtable = mw.html.create('table')
					:addClass('plainlinks')
					:addClass('infobox')
					:addClass('infobox-'..self.infobox_name)
	for _, class in ipairs(self.classes) do
		local class_name = self:get_param(class, self.default_version)
		if type(class_name) == 'string' then
			self.rtable:addClass('infobox-'..mw.ustring.gsub(class_name, '%s', '_'))
		end
	end
	-- Create the switch datatable if multiple versions
	if self.versions > 1 then
		self.rtable:addClass('infobox-switch')
		self.switch_datatable = mw.html.create('div')
									:addClass('infobox-switch-resources')
									:addClass('infobox-resources-'..self.infobox_name)
									:addClass('hidden')
	end
	return self
end


--[[
	If multiple versions exist, creates a set of buttons for switching
	-- self.max_buttons : if the number of versions exceeds this value, a dropdown will be created instead
--]]
function Infobox:buttons()
	if self.versions < 2 then
		return
	end
	-- Button caption
	local buttons = self.rtable:tag('caption')
								:tag('div')
								:addClass('infobox-buttons')
								:attr('data-default-index', self.default_version)
	-- Dropdown list instead of buttons if too many versions
	if self.versions > self.max_buttons then
		buttons:addClass('infobox-buttons-select')
	end
	-- Create all the buttons
	for version=1, self.versions do
		local button = buttons:tag('span')
			   :attr('data-switch-index', version)
			   :attr('data-switch-anchor', '#'..self.version_names[version])
			   :addClass('button')
			   :wikitext(self.version_names[version])
	    -- In case of dropdown list, hide the buttons as the switch gadget will convert them to a dropdown list - we can't directly create the dropdown list here as the select/option tags are rejected by wikitext
		if self.versions > self.max_buttons then
			button:addClass('hidden')
		end
	end
end


--[[
	For each version, stores a subobject for all params that have smw_property defined
--]]
function Infobox:store_smw()
	for version=1, self.versions do
		-- Generate a subobject name
		-- Reminder - subobject name cannot have a . in the first 5 characters
		local subobject_name = self.version_names[version]
		-- Store each param that has smw_property defined and is not nil
		local subobject = {Infobox = self.infobox_name} -- Also store the current Infobox name
		for _, param_name in ipairs(self.param_names) do
			local property = self.params[param_name].smw_property
			if property then
				local value = self:get_param(self.smw_param(param_name), version)
				if value ~= nil then
					subobject[property] = value
				end
			end
		end
		-- Keep a copy for debugging
		self.__smw_debug[subobject_name] = subobject
		-- Save subobjects if not in mainspace
		if mw.title.getCurrentTitle():inNamespace(0) then
			local result = true
			if self.versions == 1 then
				result = mw.smw.set(subobject)
			else
				result = mw.smw.subobject(subobject, subobject_name)
			end
			if result ~= true then
				table.insert(self.errors, 'SMW error: '..result.error)
			end
		end
	end
end


--[[
	Automatically add categories for each param as defined in define_params()
--]]
function Infobox:parse_categories()
	for _, param_name in ipairs(self.param_names) do
		local param = self.params[param_name]
		local defined = self:is_param_defined(Infobox.param(param_name))
		if defined == 0 and param.category_never then
			self.categories[param.category_never] = 1
		end
		if defined == 1 and param.category_partial then
			self.categories[param.category_partial] = 1
		end
		if defined < 2 and param.category_incomplete then
			self.categories[param.category_incomplete] = 1
		end
		if defined == 2 and param.category_complete then
			self.categories[param.category_complete] = 1
		end
	end
end


--[[
	When a parameter is added into the table, add the alternative versions' data into the switch datatable
	-- content : a non-table value (string, number, etc), or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
	Returns the name of the parameter in the tag 'data-attr-param'
--]]
function Infobox:add_switch_data(content)
	if self.versions < 2 then
		return false
	end
	-- Only needed for non-constant params
	if type(content) ~= 'table' then
		return false
	end
	-- Only needed if the param changes value between different versions
	local first_value = self:get_param(content, 1)
	local all_same = true
	for version=2, self.versions do
		if first_value ~= self:get_param(content, version) then
			all_same = false
			break
		end
	end
	if all_same then
		return false
	end
	-- Let's build the datatable
	-- datum name: Prepend raw__ or smw__ if not a parsed argument
	local name = content.param_name
	if content.property == 'args_raw' then
		name = 'raw__' + name
	end
	if content.property == 'args_smw' then
		name = 'smw__' + name
	end
	local data_param = self.switch_datatable:tag('span'):attr('data-attr-param', name)
	-- Add each version to the datatable
	for version=1, self.versions do
		local text = self:get_param(content, version)
		if text == nil then
			text = self.params[content.param_name].empty
		end
		data_param:tag('span'):attr('data-attr-index', version):wikitext(text)
	end
	-- return the 'data-attr-param' name
	return name
end


--[[
	Override tostring
--]]
function Infobox:__tostring()
	-- Create error messages
	local error_text = ''
	for _, v in ipairs(self.errors) do
		error_text = error_text..'<span class="mw-message-box-error">'..v..'</span>'
	end
	-- Create categories
	local category_text = ''
	if mw.title.getCurrentTitle():inNamespace(0) then
		for key, _ in pairs(self.categories) do
			category_text = category_text..'[[Category:'..key..']]'
		end
	end
	local dump = ''
	if self.args_raw.__dump then
		setmetatable(self, nil)
		dump = '<nowiki>'..mw.dumpObject(self)..'</nowiki>'
		setmetatable(self, Infobox)
	end
	return tostring(self.rtable) .. tostring(self.switch_datatable) .. error_text .. category_text .. dump
end


--[[
	Debug function
--]]
function Infobox:dump()
	-- Temporarily detach the metatable so we don't override tostring anymore, allowing us to use dumpObject
	setmetatable(self, nil)
	mw.log(mw.dumpObject(self))
	setmetatable(self, Infobox)
	mw.log(tostring(self))
end


return Infobox