Module:Infobox: Difference between revisions
Jump to navigation
Jump to search
(Blanked the page) Tags: Blanking Reverted |
No edit summary |
||
(5 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
--[=[ | |||
-- For documentation, see [[Module:Infobox/doc]] | |||
--]=] | |||
-- <nowiki> | |||
local Infobox = {} | |||
Infobox.__index = Infobox | |||
Infobox.__tostring = Infobox.tostring | |||
-- Edit button for unknown params | |||
local editbutton = require('Module:Edit button') | |||
local var = mw.ext.VariablesLua | |||
local edit = editbutton("'''?''' (edit)") | |||
-- Page title | |||
local pagename = mw.title.getCurrentTitle().fullText | |||
-- map of flags to html tags used by Infobox.addRow() | |||
-- let's only define it once, since :addRow() is used multiple times per module | |||
local tagmap = { | |||
tr = 'tr', | |||
th = 'th', | |||
td = 'td', | |||
argh = 'th', | |||
argd = 'td' | |||
} | |||
--[=[ | |||
-- Standardised functions | |||
-- called as string with defineParams | |||
--]=] | |||
-- Standardised "has content" function | |||
function hasContent(arg, default) | |||
-- Return arg if any non-whitespace character is found | |||
return string.match(arg or '','%S') and arg or default | |||
end | |||
-- Standardised "name" function | |||
function subjectName(arg) | |||
return string.match(arg or '','%S') and arg or nil | |||
end | |||
-- Create a standardised release function, since so many pages use it | |||
-- Turns release and update into a single parameter | |||
function releaseUpdate(release, update) | |||
if not Infobox.isDefined(release) then | |||
return nil | |||
end | |||
if string.lower(release) == 'no' then | |||
return 'N/A' | |||
end | |||
if not Infobox.isDefined(update) then | |||
return string.format('%s (Update unknown)',release) | |||
end | |||
if string.lower(update) == 'no' then | |||
return release | |||
end | |||
return string.format('%s ([[Update:%s|Update]])', release, update) | |||
end | |||
-- Standardised image function | |||
function image(img) | |||
if img and img:find('%S') then | |||
return img | |||
else | |||
return nil | |||
end | |||
end | |||
-- Standardised numbers | |||
function numbers(num) | |||
num = string.gsub(num or '',',','') | |||
return tonumber(num) | |||
end | |||
-- Wrap content with line breaks if it contains list-like wiki syntax | |||
function wrapContent(content) | |||
if type(content) == "string" then | |||
local firstListItem = math.min(content:find('^[*#]') or math.huge, content:find('\n[*#]') or math.huge) | |||
if firstListItem ~= math.huge then | |||
local suffix = content:find('\n[*#][^\n]+$') and '\n' or '' | |||
content = content:sub(1, firstListItem - 1) .. '\n' .. content:sub(firstListItem) .. suffix | |||
end | |||
end | |||
return content | |||
end | |||
-- map of names to pre-defined functions, used by Infobox:defineParams | |||
local func_map = { | |||
name = subjectName, | |||
release = { name = releaseUpdate, params = { 'release', 'update' }, flag = 'p' }, | |||
removal = { name = releaseUpdate, params = { 'removal', 'removalupdate' }, flag = 'p' }, | |||
has_content = hasContent, | |||
hasContent = hasContent, | |||
image = image, | |||
numbers = numbers, | |||
} | |||
-- used to fill nil params in switching sections | |||
-- this message isn't kidding | |||
-- If you see this message anywhere outside of this code | |||
-- (including inside switchfo box data) | |||
-- report it | |||
local nil_param = 'UH OH YOU SHOULDN\'T SEE THIS!' | |||
-- In case the nil_param is needed outside of this module | |||
-- give it an easy way to be accessed | |||
function Infobox.nilParam() | |||
return nil_param | |||
end | |||
-- switch infobox globals | |||
local LINE_WIDTH = 300 | |||
local MAX_LINES = 2 | |||
local DEFAULT_MAX_BUTTONS = 6 | |||
-- calculate with width of a switch infobox button | |||
-- potential @TODO: rework to use actual character widths | |||
function button_width(label) | |||
local PX_PER_CHAR = 6 | |||
local PX_PAD_MAR = 24 | |||
return string.len(label) * PX_PER_CHAR + PX_PAD_MAR | |||
end | |||
Infobox.splitpoint = '&&SPLITPOINT&&' | |||
-- quick test to see if a value is considered nil | |||
function Infobox.isDefined(arg) | |||
if arg == nil then | |||
return false | |||
end | |||
if type(arg) == 'string' then | |||
if arg == nil_param then | |||
return false | |||
elseif arg:find('%S') then | |||
if arg:find('action=edit') then | |||
return false | |||
else | |||
return true | |||
end | |||
else | |||
return false | |||
end | |||
end | |||
return true | |||
end | |||
--[[ | |||
Infobox class | |||
-- args : parameters from frame to pass through | |||
-- Sets a meta table and creates a <div> tag wrapper | |||
-- other fields are initialised in other functions | |||
--]] | |||
function Infobox.new(args) | |||
local obj = setmetatable({ | |||
args = args, -- parameters (uncleaned) | |||
rargs = {}, -- parameters (cleaned) | |||
params = {}, -- parameters mapped to functions | |||
paramnames = {}, -- parameter names | |||
dupeable = {}, -- parameters that are allowed to have duplicated switch data | |||
addrswibclass = true, | |||
switchfo = false, -- switch infobox? or not? | |||
switchfoattr = {}, -- switch data class changes | |||
maxbuttons = DEFAULT_MAX_BUTTONS, -- maximum number of buttons before switching becomes a menu | |||
switch_tag = '', -- switchfo data | |||
switch_buttons_tag = '', -- switchfo buttons | |||
custom_buttons = false, | |||
smw_error_tag = '', | |||
rtable = nil, -- returned infobox table | |||
labels = nil, -- returned labels | |||
_smw = {}, -- semantic mediawiki data | |||
_smwOne = {}, -- semantic mediawiki data part 2 | |||
_smwSubobject = {}, -- semantic mediawiki data part 3 | |||
_smwSubobjectName = nil, -- semantic mediawiki data part 3.5 | |||
_smwElement = {}, -- semantic mediawiki data part 4 | |||
set_default_version_smw = false, -- whether to set [[Property:Default version]] | |||
setSMWElement = true, | |||
suppressAllSMW = false, | |||
suppressVersionSMW = {}, | |||
versions = -1, -- number of switch versions (-1 is uncalculated) | |||
infoboxname = nil, -- template name | |||
appendStrs = {}, | |||
bottomlinks = { -- template bottom links | |||
links = { | |||
{ 'Template talk:%s', 'talk' }, | |||
{ 'Template:%s', 'view' } | |||
}, | |||
colspan = 2 | |||
}, | |||
catdata = {}, -- meta category data | |||
catlist = {}, -- defined table of category names (strings) | |||
__finished = false, -- infobox status | |||
}, | |||
Infobox) | |||
return obj | |||
end | |||
--[[ | |||
Toggles the addition of infobox class | |||
use before :create() | |||
noop if not a boolean | |||
--]] | |||
function Infobox:setAddRSWInfoboxClass(bool) | |||
if type(bool) == 'boolean' then | |||
self.addrswibclass = bool | |||
end | |||
end | |||
--[[ | |||
Creates an infobox | |||
-- If Infobox:maxVersions() has not been run, it will be run here | |||
-- If the infobox should be a switch infobox, all labels will be added | |||
-- Creates a wikitable that will be the infobox | |||
THIS SHOULD BE DONE AFTER ADDING AND CLEANING PARAMETERS | |||
--]] | |||
function Infobox:create() | |||
-- Run to find if this is a switch infobox and if so, how many boxes | |||
if self.versions == -1 then | |||
self:maxVersion() | |||
end | |||
-- Run if switch infobox | |||
if self.switchfo then | |||
-- Buttons wrapper | |||
-- Hidden by default, unhidden by javascript | |||
self.switch_buttons_tag = mw.html.create('div') | |||
:addClass('infobox-buttons') | |||
-- default version to immediately switch to via js | |||
local defv = tonumber(self.args.defver) | |||
if defv and defv <= self.versions then -- you troll, don't try to show something that isn't there | |||
self.switch_buttons_tag:attr('data-default-version',defv) | |||
end | |||
local numlines = 1 | |||
local width_working = 0 | |||
local total_width = 0 | |||
local buttons = {} | |||
-- Add individual buttons to the wrapper | |||
for i=1,self.versions do | |||
local wid = button_width(self.labels[i] or i) | |||
width_working = width_working + wid | |||
total_width = total_width + wid | |||
if width_working > LINE_WIDTH then | |||
numlines = numlines + 1 | |||
width_working = wid | |||
end | |||
local b = mw.html.create('span') | |||
:attr('data-switch-index',tostring(i)) | |||
-- space to underscore | |||
:attr('data-switch-anchor','#'..string.gsub(self.labels[i] or i,' ','_')) | |||
:addClass('button') | |||
:wikitext(self.labels[i] or i) | |||
table.insert(buttons, {b, wid}) | |||
end | |||
local best = {-1, 100000} | |||
if (numlines > 1) and (numlines <= MAX_LINES) then | |||
-- attempt to balance line widths | |||
local w_s, w_e = 0,total_width | |||
for i = 1,#buttons-1 do | |||
w_s = w_s + buttons[i][2] | |||
w_e = w_e - buttons[i][2] | |||
if w_s > LINE_WIDTH then | |||
-- w_s only increases, so we're done once it exceeds the width | |||
break | |||
end | |||
if w_e <= LINE_WIDTH then | |||
-- w_e only decreases, so just continue if it exceeds line | |||
local diff = math.abs(w_s - w_e) | |||
if diff < best[2] then | |||
best = { i, diff } | |||
end | |||
end | |||
end | |||
if best[1] == -1 then | |||
best = { math.floor(#buttons/2), 100000 } | |||
end | |||
end | |||
for i,v in ipairs(buttons) do | |||
self.switch_buttons_tag:node(v[1]) | |||
if i == best[1] then | |||
self.switch_buttons_tag:tag('span'):addClass('line-break') | |||
end | |||
end | |||
-- Used by JavaScript to turn the buttons into a menu list if too many variants | |||
if self.versions > self.maxbuttons or numlines > MAX_LINES then | |||
self.switch_buttons_tag:addClass('infobox-buttons-select') | |||
end | |||
self.switch_buttons_tag:done() | |||
end | |||
-- Create infobox table | |||
self.rtable = mw.html.create('table') | |||
if self.addrswibclass then | |||
self.rtable:addClass('infobox') | |||
end | |||
-- Add necessary class if switch infobox | |||
if self.switchfo then | |||
self.rtable:addClass('infobox-switch') | |||
end | |||
end | |||
-- Defines an infobox name ({{Template:arg}}) | |||
-- Used to create a link at the bottom of pages | |||
function Infobox:defineName(arg) | |||
self.infoboxname = arg | |||
end | |||
-- Defines the bottom links of the infobox | |||
-- pass a table whose elements are tables that define a link and a label | |||
-- { | |||
-- { 'link', 'label }, | |||
-- ... | |||
-- } | |||
-- The template name can be substituted into the tables using '%s' | |||
-- If we wanted Template:InFooBar to link to it's /doc page with a "doc" label: | |||
-- { ... | |||
-- { 'Template:%s/doc', 'doc' }, | |||
-- ... } | |||
-- The template's name can only be called 5 times | |||
function Infobox:defineLinks(arg) | |||
if type(arg) == 'table' then | |||
if arg.colspan then | |||
self.bottomlinks.colspan = arg.colspan | |||
end | |||
if arg.links then | |||
if type(arg.links) == 'table' then | |||
self.bottomlinks.links = arg.links | |||
end | |||
end | |||
if arg.hide then | |||
self.bottomlinks.hide = arg.hide | |||
end | |||
end | |||
end | |||
-- Change max number of buttons before switching to menu | |||
-- defaults to 5 | |||
-- MUST BE RUN BEFORE :create() | |||
function Infobox:setMaxButtons(arg) | |||
-- if not a number, just go back to default | |||
self.maxbuttons = tonumber(arg) or DEFAULT_MAX_BUTTONS | |||
end | |||
--[[ | |||
Add parameters functions | |||
All parameters should be tables | |||
The first parameter defines the type of cell to create | |||
-- th : <th> | |||
-- td : <td> | |||
-- argh : <th> | |||
-- argd : <td> | |||
The second parameter defines what is inside the tag | |||
-- th | th : text passed | |||
-- argh | argd : parameter with the name passed | |||
Additional named parameters can be used to add any styling or attributes | |||
-- attr : mw.html:attr({ arg1 = '1', ... }) | |||
-- css : mw.html:css({ arg1 = '1', ...) | |||
-- class : mw.html:addClass('arg') | |||
---- class also supports a table of values, even though mw.html:addClass() does not | |||
-- rowspan : mw.html:attr('rowspan',arg) | |||
-- colspan : mw.html:attr('colspan',arg) | |||
-- title : mw.html:attr('title',arg) | |||
Example: | |||
ipsobox:addRow( { 'th' , 'Header', title = 'Title' }, | |||
{ 'argh', 'arg1', class = 'parameter' } }) | |||
produces: | |||
<tr><th title="Title">Header</th><th class="parameter">args.arg1</th></tr> | |||
adding it to the infobox table of ipsobox | |||
Cells defined as 'argh' and 'argd' will automatically have data-attr-param="" added, and defined as the passed argument if the infobox in creation is defined as a switch infobox | |||
The row itself may be modified with metadata using the named index at "meta" | |||
-- meta.addClass : mw.html:addClass('arg') | |||
-- this function currently only supports a single string | |||
--]] | |||
function Infobox.addRow(box, ...) | |||
-- New row to add | |||
local args = ... | |||
local _row = box.rtable:tag('tr') | |||
-- For each member of tags | |||
for i, v in ipairs(args) do | |||
-- map tag name to appropriate tag, default to <td> | |||
local _cell = _row:tag(tagmap[v.tag] or 'td') | |||
-- mw.html:attr() and mw.html:css() both accept table input | |||
-- colspan, rowspan, title will be quick ways to access attr | |||
-- these functions also do all the necessary work | |||
if v.attr then | |||
_cell:attr(v.attr) | |||
end | |||
if v.colspan then | |||
_cell:attr('colspan',v.colspan) | |||
end | |||
if v.rowspan then | |||
_cell:attr('rowspan',v.rowspan) | |||
end | |||
if v.title then | |||
_cell:attr('title',v.title) | |||
end | |||
if v.css then | |||
_cell:css(v.css) | |||
end | |||
-- if class is a string, it can be added directly | |||
-- if a table, add every value | |||
-- mw.html:addClass() doesn't function with tables | |||
-- so iterate over the class names here and add them individually | |||
if v.class then | |||
if type(v.class) == 'string' then | |||
_cell:addClass(v.class) | |||
elseif type(v.class) == 'table' then | |||
for _, w in ipairs(v.class) do | |||
_cell:addClass(w) | |||
end | |||
end | |||
end | |||
-- if the cell is a normal th or td, add the exact argument passed | |||
if v.tag == 'th' or v.tag == 'td' then | |||
_cell:wikitext(wrapContent(v.content)) | |||
-- if defined with "arg", add the argument with name passed | |||
elseif v.tag == 'argh' or v.tag == 'argd' then | |||
local content = box.rargs[v.content] | |||
-- if the requested parameter doesn't exist whatsoever, just return a blank string | |||
if not content then | |||
content = '' | |||
-- If switches exist, first attempt to use the version1 values | |||
elseif content.switches then | |||
if content.switches[1] ~= nil_param then | |||
content = content.switches[1] or '' | |||
else | |||
content = content.d or '' | |||
end | |||
-- fallback to default value | |||
else | |||
content = content.d or '' | |||
end | |||
_cell:wikitext(wrapContent(content)) | |||
-- add necessary attribute for switch infoboxes | |||
if box.switchfo then | |||
_cell:attr('data-attr-param',v.content) | |||
end | |||
end | |||
end | |||
-- not that meta | |||
-- allow classes to be defined on the whole row | |||
-- okay, sort of meta | |||
if args.meta then | |||
if args.meta.addClass then | |||
_row:addClass(args.meta.addClass) | |||
end | |||
end | |||
return box | |||
end | |||
function Infobox.customButtonPlacement(box,arg) | |||
box.custom_buttons = arg | |||
return box | |||
end | |||
-- Choose whether to set [[Property:Default version]] | |||
-- Defaults to false. | |||
function Infobox.setDefaultVersionSMW(box, arg) | |||
box.set_default_version_smw = arg | |||
return box | |||
end | |||
function Infobox.addButtonsRow(box, args) | |||
if box.switchfo then | |||
box.custom_buttons = true | |||
local _row = box.rtable:tag('tr') | |||
:addClass('infobox-switch-buttons-row') | |||
:tag('td') | |||
:addClass('infobox-switch-buttons') | |||
:attr('colspan', args.colspan) | |||
:node(box.switch_buttons_tag) | |||
end | |||
return box | |||
end | |||
function Infobox.addButtonsCaption(box) | |||
if box.switchfo then | |||
box.custom_buttons = true | |||
local _row = box.rtable:tag('caption') | |||
:addClass('infobox-switch-buttons-caption') | |||
:node(box.switch_buttons_tag) | |||
end | |||
return box | |||
end | |||
--[[ | |||
-- adds a blank row of padding spanning the given number of columns | |||
--]] | |||
function Infobox.pad(box, colspan, class) | |||
local tr = box:tag('tr') | |||
:tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding') | |||
:done() | |||
if class then | |||
tr:addClass(class) | |||
end | |||
tr:done() | |||
return box | |||
end | |||
--[[ | |||
-- functions the same as mw.html:wikitext() on the wrapper | |||
-- Should only be used for categories really | |||
--]] | |||
function Infobox.wikitext(box, arg) | |||
box.rtable:wikitext(arg) | |||
return box | |||
end | |||
--[[ | |||
-- Adds the specified item(s) to the end of the infobox, outside of the table | |||
-- items are concatenated together with an empty space | |||
--]] | |||
function Infobox.append(box, ...) | |||
for i,v in ipairs({...}) do | |||
table.insert(box.appendStrs, v) | |||
end | |||
return box | |||
end | |||
--[[ | |||
-- Adds a caption to the infobox | |||
-- defaults to the pagename | |||
-- or the default argument if defined | |||
--]] | |||
function Infobox.caption(box) | |||
-- default to the article's name | |||
local name = pagename | |||
-- first see if the name parameter exists | |||
if box.rargs.name then | |||
-- then try the default | |||
if box.rargs.name.d then | |||
name = box.rargs.name.d | |||
-- then look for swithes | |||
elseif box.rargs.name.switches then | |||
-- then look at version 1 | |||
if box.rargs.name.switches[1] ~= nil_param then | |||
name = box.rargs.name.switches[1] | |||
end | |||
end | |||
end | |||
local caption = box.rtable:tag('caption') | |||
:wikitext(name) | |||
-- add necessary attribute for switch infoboxes | |||
if box.switchfo then | |||
caption:attr('data-attr-param','name') | |||
end | |||
return box | |||
end | |||
--[[ | |||
-- Functions for styling the infobox | |||
-- works the same as the respective mw.html functions | |||
--]] | |||
-- attr | |||
function Infobox.attr(box, arg) | |||
box.rtable:attr(arg) | |||
return box | |||
end | |||
-- css | |||
function Infobox.float(box,float) | |||
box.rtable:css('float',float) | |||
return box | |||
end | |||
function Infobox.css(box, ...) | |||
box.rtable:css(...) | |||
return box | |||
end | |||
-- addClass | |||
function Infobox.addClass(box, arg) | |||
box.rtable:addClass(arg) | |||
return box | |||
end | |||
-- Much like Infobox.addClass, but adds multiple classes | |||
function Infobox.addClasses(box, ...) | |||
for _, v in ipairs(...) do | |||
box.rtable:addClass(box) | |||
end | |||
return box | |||
end | |||
--[[ | |||
Add tags directly to the infobox table | |||
Use sparingly | |||
Returns the tag created rather than the entire box | |||
Which is an mw.html object | |||
Further uses of :tag() will be mw.html.tag, rather than Infobox.tag | |||
As such, Infobox:addRow() cannot be used afterwards without restating the infobox as the object | |||
--]] | |||
function Infobox.tag(box, arg) | |||
return box.rtable:tag(arg) | |||
end | |||
--[[ | |||
Allows the infobox to use Semantic Media Wiki and give parameters properties | |||
Pass a table to this function to map parameter names to properties | |||
Calling syntax: | |||
-- {{#show:page|?property}}: | |||
-- "<property>" - unqualified and without a number will display the default value | |||
-- "<property#>" - with a number will show the switch data from that index | |||
-- "all <property>" - adding all will display every unique value in a comma separated list | |||
Properties initiated in Infobox:finish() | |||
--]] | |||
function Infobox:useSMW(arg) | |||
if type(arg) == 'table' then | |||
for w, v in pairs(arg) do | |||
self._smw[w] = v | |||
end | |||
end | |||
end | |||
--[[ | |||
As above, but only assigns to "<property>", which will act like "all <property>" - "<property>#" not present | |||
Properties initiated in Infobox:finish() | |||
--]] | |||
function Infobox:useSMWOne(arg) | |||
if type(arg) == 'table' then | |||
for w, v in pairs(arg) do | |||
self._smwOne[w] = v | |||
end | |||
end | |||
end | |||
--[[ | |||
Set up the infobox to set properties in a SMW subobject. This will create a subobject for each version | |||
- if there is only one version, it will put the properties directly on to the page, like useSMWOne | |||
Properties initiated in Infobox:finish() | |||
--]] | |||
function Infobox:useSMWSubobject(arg) | |||
if type(arg) == 'table' then | |||
for w, v in pairs(arg) do | |||
self._smwSubobject[w] = v | |||
end | |||
end | |||
end | |||
function Infobox:useSMWElement(arg) | |||
if type(arg) == 'table' then | |||
for w, v in pairs(arg) do | |||
self._smwElement[w] = v | |||
end | |||
self.setSMWElement = true | |||
end | |||
end | |||
--[[ | |||
Finishing function | |||
-- Finishes the return, adding necessary final tags | |||
--]] | |||
function Infobox:finish() | |||
local currentNamespace = mw.title.getCurrentTitle().namespace | |||
-- 0 = mainspace, 4 = RuneScape | |||
local onContentNamespace = currentNamespace == 0 or currentNamespace == 4 | |||
-- Don't finish twice | |||
if self.__finished then | |||
return | |||
end | |||
-- Add switch infobox resources | |||
if self.switchfo then | |||
self.rtable:attr('data-resource-class', '.infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_')) | |||
-- Wrapper tag, hidden | |||
self.switch_tag = mw.html.create('div') | |||
:addClass('infobox-switch-resources') | |||
:addClass('infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_')) | |||
:addClass('hidden') | |||
for _, v in ipairs(self.paramnames) do | |||
local param = self.rargs[v] | |||
local default_value = param.d or edit | |||
-- Parameters may not have any switches data, those are ignored | |||
local switchattr = self.switchfoattr[v] | |||
-- Parameter data wrapper | |||
local res_span = self.switch_tag:tag('span') | |||
:attr('data-attr-param',v) | |||
-- Child for default value | |||
local def = res_span:tag('span') | |||
:attr('data-attr-index',0) | |||
:wikitext(tostring(default_value)) | |||
-- Switch classes | |||
if switchattr then | |||
def:attr('data-addclass',switchattr.d) | |||
end | |||
def:done() | |||
if param.switches then | |||
-- Add all switches, ignore those defined as nil | |||
for i, w in ipairs(param.switches) do | |||
if w ~= nil_param and w ~= nil and w ~= default_value then | |||
local _w = res_span:tag('span') | |||
:attr('data-attr-index',i) | |||
:wikitext(tostring(w)) | |||
-- Switch classes | |||
if switchattr and switchattr.switches then | |||
_w:attr('data-addclass',switchattr.switches[i]) | |||
elseif switchattr then | |||
mw.logObject({string.format("Expected switches for `%s` but got none:", v), switchattr}) | |||
end | |||
_w:done() | |||
end | |||
end | |||
res_span:done() | |||
end | |||
end | |||
-- Add a tracking category for pages that have more than 1 version | |||
if onContentNamespace and self.versions > 1 then | |||
-- version count data | |||
self.switch_tag:wikitext('[[Category:Pages that contain switch infobox data]]') | |||
if not self.suppressAllSMW then | |||
self.switch_tag:tag('span') | |||
:wikitext(string.format('Versions: [[Version count::%s]]',self.versions)) | |||
:done() | |||
-- set default version smw | |||
local defver = tonumber(self.args.defver) or 1 | |||
local default_subobject_value = self.args['smwname'..defver] or self.args['version'..defver] | |||
if default_subobject_value and self.set_default_version_smw then | |||
-- Only take first subobject if multiple are defined using "¦" | |||
local default_subobject_name = default_subobject_value:match("[^¦]+") | |||
self.switch_tag:tag('span') | |||
:wikitext(string.format('Default version: [[Default version::%s]]',default_subobject_name)) | |||
end | |||
end | |||
end | |||
self.switch_tag:done() | |||
end | |||
-- smw data | |||
if onContentNamespace and not self.suppressAllSMW then | |||
-- members smw display, yes --> true; no --> false; other --> unknown | |||
local function smwMembers(smw_arg) | |||
local smw_argv = string.lower(smw_arg or '') | |||
if smw_argv == 'yes' then | |||
return 'true' | |||
elseif smw_argv == 'no' then | |||
return 'false' | |||
else | |||
return 'unknown' | |||
end | |||
end | |||
-- release date smw display | |||
local function smwRelease(smw_arg) | |||
local _d,_m,_y = string.match(smw_arg or '', '%[%[(%d%d?) (%a+)%]%] %[%[(%d%d%d%d)%]%]') | |||
if _d == nil then | |||
return nil | |||
end | |||
return table.concat({_d,_m,_y},' ') | |||
end | |||
-- default, just return the text | |||
local function smwDefault(smw_arg) | |||
if smw_arg ~= nil_param and smw_arg ~= edit then | |||
return smw_arg | |||
else | |||
return 'unknown' | |||
end | |||
end | |||
local smw_to_func = { | |||
members = smwMembers, | |||
release = smwRelease, | |||
removal = smwRelease, | |||
default = smwDefault | |||
} | |||
local smw_data_arr = {} | |||
-- custom properties | |||
for w, v in pairs(self._smw) do | |||
-- only needed to give special formatting to release | |||
-- and to make members true/false | |||
local smwfunc = smw_to_func[w] or smw_to_func.default | |||
local curarg = self.rargs[w] | |||
if curarg then | |||
local _arg = curarg.d | |||
local argdefault = _arg | |||
if _arg == edit then | |||
argdefault = 'unknown' | |||
else | |||
local _x = mw.text.split(tostring(_arg), Infobox.splitpoint, true) | |||
if not smw_data_arr[v] then | |||
smw_data_arr[v] = {} | |||
end | |||
if not smw_data_arr['All '..v] then | |||
smw_data_arr['All '..v] = {} | |||
end | |||
for _,y in ipairs(_x) do | |||
local temp_smw_data = smwfunc(y) | |||
table.insert(smw_data_arr[v], temp_smw_data) | |||
table.insert(smw_data_arr['All '..v], temp_smw_data) | |||
end | |||
end | |||
if curarg.switches then | |||
local _args = {} | |||
for x_i, x in ipairs(curarg.switches) do | |||
if not self.suppressVersionSMW[x_i] then | |||
if x ~= nil_param then | |||
table.insert(_args,x) | |||
else | |||
table.insert(_args,argdefault or nil_param) | |||
end | |||
end | |||
end | |||
for i, x in ipairs(_args) do | |||
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true) | |||
if not smw_data_arr[v..i] then | |||
smw_data_arr[v..i] = {} | |||
end | |||
if not smw_data_arr['All '..v] then | |||
smw_data_arr['All '..v] = {} | |||
end | |||
for _,y in ipairs(_x) do | |||
local temp_smw_data = smwfunc(y) | |||
table.insert(smw_data_arr[v..i], temp_smw_data) | |||
table.insert(smw_data_arr['All '..v], temp_smw_data) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
-- if one version, put smwSubobject into smwOne and just do that | |||
if self.versions < 2 and not self._smwSubobjectName then | |||
for w,v in pairs(self._smwSubobject) do | |||
if not self._smwOne[w] then | |||
self._smwOne[w] = v | |||
elseif type(self._smwOne[w]) == 'table' then | |||
table.insert(self._smwOne[w], v) | |||
else | |||
self._smwOne[w] = { self._smwOne[w], v } | |||
end | |||
end | |||
end | |||
for w, _v in pairs(self._smwOne) do | |||
-- only needed to give special formatting to release | |||
-- and to make members true/false | |||
local smwfunc = smw_to_func[w] or smw_to_func.default | |||
local curarg = self.rargs[w] | |||
if curarg then | |||
local _arg = curarg.d | |||
local argdefault = _arg | |||
if _arg == edit then | |||
argdefault = 'unknown' | |||
end | |||
if curarg.switches then | |||
local _args = {} | |||
for x_i, x in ipairs(curarg.switches) do | |||
if not self.suppressVersionSMW[x_i] then | |||
if x ~= nil_param then | |||
table.insert(_args,x) | |||
else | |||
table.insert(_args,argdefault or nil_param) | |||
end | |||
end | |||
end | |||
for i, x in ipairs(_args) do | |||
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true) | |||
for _,y in ipairs(_x) do | |||
local temp_smw_data = smwfunc(y) | |||
for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do | |||
if not smw_data_arr[v] then | |||
smw_data_arr[v] = {} | |||
end | |||
table.insert(smw_data_arr[v], temp_smw_data) | |||
end | |||
end | |||
end | |||
else | |||
if Infobox.isDefined(_arg) then | |||
local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true) | |||
for _,y in ipairs(_targ) do | |||
local temp_smw_data = smwfunc(y) | |||
for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do | |||
if not smw_data_arr[v] then | |||
smw_data_arr[v] = {} | |||
end | |||
table.insert(smw_data_arr[v], temp_smw_data) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
local smw_data_arr_elem = {} | |||
for w, v in pairs(self._smwElement) do | |||
-- only needed to give special formatting to release | |||
-- and to make members true/false | |||
local smwfunc = smw_to_func[w] or smw_to_func.default | |||
local curarg = self.rargs[w] | |||
if curarg then | |||
local _arg = curarg.d | |||
local argdefault = _arg | |||
if _arg == edit then | |||
argdefault = 'unknown' | |||
end | |||
if curarg.switches then | |||
local _args = {} | |||
for x_i, x in ipairs(curarg.switches) do | |||
if not self.suppressVersionSMW[x_i] then | |||
if x ~= nil_param then | |||
table.insert(_args,x) | |||
else | |||
table.insert(_args,argdefault or nil_param) | |||
end | |||
end | |||
end | |||
for i, x in ipairs(_args) do | |||
if Infobox.isDefined(x) then | |||
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true) | |||
for _,y in ipairs(_x) do | |||
local temp_smw_data = smwfunc(y) | |||
if not smw_data_arr_elem[v] then | |||
smw_data_arr_elem[v] = {} | |||
end | |||
table.insert(smw_data_arr_elem[v], temp_smw_data) | |||
end | |||
end | |||
end | |||
else | |||
if Infobox.isDefined(_arg) then | |||
local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true) | |||
for _,y in ipairs(_targ) do | |||
local temp_smw_data = smwfunc(y) | |||
if not smw_data_arr_elem[v] then | |||
smw_data_arr_elem[v] = {} | |||
end | |||
table.insert(smw_data_arr_elem[v], temp_smw_data) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
-- if is a switchfo, setup for subobjects | |||
local smw_data_arr_subobj = {} | |||
if self._smwSubobjectName then | |||
for i,k in ipairs(self._smwSubobjectName) do | |||
if not self.suppressVersionSMW[i] then | |||
local subobj_data = { | |||
['Is variant of'] = {pagename}, | |||
} | |||
for w,v in pairs(self._smwSubobject) do | |||
local smwfunc = smw_to_func[w] or smw_to_func.default | |||
local curarg = self.rargs[w] | |||
if curarg then | |||
local argval = curarg.d | |||
if curarg.switches then | |||
argval = curarg.switches[i] | |||
if not Infobox.isDefined(argval) then | |||
argval = curarg.d | |||
end | |||
end | |||
if Infobox.isDefined(argval) then | |||
local _x = mw.text.split(tostring(argval), Infobox.splitpoint, true) | |||
for _, _x1 in ipairs(_x) do | |||
_x1 = smwfunc(_x1) | |||
if _x1 ~= 'unknown' then | |||
if not subobj_data[v] then | |||
subobj_data[v] = {} | |||
end | |||
table.insert(subobj_data[v], _x1) | |||
end | |||
end | |||
end | |||
end | |||
end | |||
for w,v in ipairs(k) do | |||
local subobj_name = v | |||
-- can't have a . in the first 5 characters of a subobject identifier | |||
if mw.ustring.find(mw.ustring.sub(subobj_name,0,5), '%.') then | |||
self:wikitext('[[Category:Pages with an invalid subobject name]]') | |||
subobj_name = mw.ustring.gsub(subobj_name, '%.', '') | |||
end | |||
if subobj_name == '0' or subobj_name == '' then | |||
self:wikitext('[[Category:Pages with an invalid subobject name]]') | |||
subobj_name = 'v_'..subobj_name | |||
end | |||
smw_data_arr_subobj[subobj_name] = subobj_data | |||
end | |||
end | |||
end | |||
end | |||
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 | |||
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 | |||
end | |||
end | |||
end | |||
end | |||
if self._smwSubobjectName then | |||
if self.smw_error_tag == '' then | |||
self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data') | |||
end | |||
for w,v in pairs(smw_data_arr_subobj) do | |||
local subobjdiv = self.smw_error_tag:tag('div') | |||
subobjdiv:tag('span'):wikitext('SMW Subobject for '..w) | |||
for j,k in pairs(v) do | |||
subobjdiv:tag('span'):wikitext(mw.ustring.format('%s: %s', j, table.concat(k, ', '))) | |||
end | |||
end | |||
end | |||
end | |||
-- Add view and talk links to infobox | |||
-- Only done if a name is defined | |||
if self.infoboxname and not self.bottomlinks.hide then | |||
local bottom_links = {} | |||
for _, v in ipairs(self.bottomlinks.links) do | |||
table.insert(bottom_links, | |||
string.format( | |||
table.concat({'[[', | |||
v[1], | |||
'|', | |||
v[2], | |||
']]'}), | |||
self.infoboxname, | |||
self.infoboxname, | |||
self.infoboxname, | |||
self.infoboxname, | |||
self.infoboxname) | |||
) | |||
end | |||
bottom_links = table.concat(bottom_links,' • ') | |||
self.rtable:tag('tr'):tag('td') | |||
:addClass('infobox-template-links') | |||
:attr('colspan', self.bottomlinks.colspan) | |||
:wikitext(bottom_links) | |||
:done() | |||
end | |||
-- Define as finished | |||
self.__finished = true | |||
end | |||
--[[ | |||
Function for defining parameters | |||
-- name : parameter name | |||
-- func : function to define param, defaults to looking at blanks | |||
DO NOT DEFINE VERSION HERE | |||
USE :maxVersion() | |||
Can be used any number of times for efficient definition | |||
--]] | |||
function Infobox:defineParams(...) | |||
for _, v in ipairs(...) do | |||
-- For every parameter, store its corresponding function to self.params | |||
if v.name then | |||
-- If the value is a function or a table (which should define a function) | |||
if type(v.func) == 'function' or type(v.func) == 'table' then | |||
self.params[v.name] = v.func | |||
-- If the value is a string, use the predefined Infobox function of that name | |||
elseif type(v.func) == 'string' then | |||
self.params[v.name] = func_map[v.func] or hasContent | |||
-- Everything else just looks for blanks | |||
else | |||
self.params[v.name] = hasContent() | |||
end | |||
-- Create a list of all param names | |||
table.insert(self.paramnames,v.name) | |||
-- function to allow duplicated values | |||
if v.dupes then | |||
self.dupeable[v.name] = true | |||
end | |||
end | |||
end | |||
end | |||
--[[ | |||
-- Forces an infobox to only use 1 variant | |||
-- Mainly used by lite infoboxes | |||
-- This should be run before creation | |||
--]] | |||
function Infobox:noSwitch() | |||
self.versions = 1 | |||
self.switchfo = false | |||
end | |||
--[[ | |||
-- Calculates the max version | |||
-- Adds labels | |||
-- Sees if this needs to be a switch infobox | |||
-- Returns extra version count (even if already run) | |||
--]] | |||
function Infobox.maxVersion(box) | |||
-- Only allowed to run once | |||
if box.versions ~= -1 then | |||
return box.versions | |||
end | |||
box.labels = {} | |||
box.versions = 0 | |||
local smwname = {} | |||
if string.lower(box.args['smw'] or '') == 'no' then | |||
box.suppressAllSMW = true | |||
end | |||
-- Look for up to 125 variants, defined in order | |||
for i=1, 125 do | |||
-- If variant# exists | |||
if box.args['version'..i] then | |||
-- Increase version count | |||
box.versions = box.versions + 1 | |||
-- Add its label | |||
local label = box.args['version'..i] or ('Version '..i) | |||
table.insert(box.labels,label) | |||
-- add to smwname | |||
if box.args['smwname'..i] then | |||
table.insert(smwname, mw.text.split(box.args['smwname'..i], '¦')) | |||
else | |||
table.insert(smwname, {label}) | |||
end | |||
box.suppressVersionSMW[i] = (box.args['smw'..i] and string.lower(box.args['smw'..i] or '') == 'no') | |||
else | |||
-- Stop if it doesn't exist | |||
break | |||
end | |||
end | |||
-- Define self as a switch infobox if at least 1 other version is found | |||
if box.versions > 0 then | |||
box.switchfo = true | |||
box._smwSubobjectName = smwname | |||
else | |||
-- single version, check for smwname | |||
if box.args['smwname'] then | |||
box._smwSubobjectName = {mw.text.split(box.args['smwname'], '¦')} | |||
end | |||
end | |||
-- versions calculated | |||
return box.versions | |||
end | |||
--[[ | |||
-- Cleans parameters as defined by the above function | |||
-- SHOULD NOT BE RUN UNTIL ALL THINGS ARE DEFINED | |||
-- Handles switches as well | |||
-- adds table _add to rargs, a cleaned up version of arguments | |||
-- d : default value | |||
-- switches : table of switches (in numerical order) | |||
-- Functions can be defined with tables | |||
---- name : name of function | |||
---- params : table of args to pass to functions | |||
---- flag : flags for input | |||
d | #default : use the cleaned parameter first, otherwise passed | |||
p : use the passed value of parameters | |||
r | l : use raw (literal) text, rather than values | |||
-- Defining a single flag will use that flag on all parameters | |||
-- Defining a table of flags will use the respective flag by position | |||
--]] | |||
function Infobox:cleanParams() | |||
-- map of flags to functionality | |||
local flagmap = { | |||
r = 'r', | |||
l = 'r', | |||
d = 'd', | |||
p = 'p' | |||
} | |||
-- For all parameters named | |||
for _, v in ipairs(self.paramnames) do | |||
-- Parameter to add | |||
local _add = {} | |||
local catdata = { all_defined = true, one_defined = false } | |||
-- If the value of params is a function | |||
if type(self.params[v]) == 'function' then | |||
-- Perform that function with the parameter | |||
_add.d = self.params[v](self.args[v]) | |||
-- If it's a table, parse it into a function | |||
elseif type(self.params[v]) == 'table' then | |||
-- Find the functions name | |||
local func = self.params[v].name | |||
if type(func) == 'string' then | |||
func = func_map[func] | |||
end | |||
-- catch all | |||
if type(func) ~= 'function' then | |||
func = has_content | |||
end | |||
-- Recreate table of args and flags | |||
local func_args = {} | |||
local flag = {} | |||
-- If the flags are NOT a table, turn them into a table | |||
-- Same size as the parameter table | |||
-- Every flag will be the same | |||
if type(self.params[v].flag) ~= 'table' then | |||
-- Map flags, if unmapped, use default | |||
local _flag = flagmap[self.params[v].flag] or 'd' | |||
-- recreate table | |||
for x=1,#self.params[v].params do | |||
table.insert(flag,_flag) | |||
end | |||
-- If flags are already a table, recreate them in new table | |||
elseif type(self.params[v].flag) == 'table' then | |||
local _flag = self.params[v].flag | |||
-- recreate table | |||
for x=1,#self.params[v].params do | |||
-- Map flags, if unmapped, use default | |||
table.insert(flag,flagmap[_flag[x]] or 'd') | |||
end | |||
end | |||
-- Recreate param table, parsing flags as instructions | |||
for x, w in ipairs(self.params[v].params) do | |||
local xarg | |||
-- By default or defined as 'd' | |||
-- looks for the cleaned value of the named parameter first | |||
-- if it doesn't exist, look at the passed value next | |||
-- if that doesn't exist, use blank | |||
if flag[x] == 'd' then | |||
xarg = self.rargs[w] and self.rargs[w].d | |||
-- compare to nil explicitly because false is a valid value | |||
if xarg == nil then | |||
xarg = self.args[w] or '' | |||
end | |||
-- Look only at the passed value of the named parameter | |||
-- if that doesn't exist, use blank | |||
elseif flag[x] == 'p' then | |||
xarg = self.args[w] or '' | |||
-- Don't interpret value as a parameter name, and paste the as is | |||
elseif flag[x] == 'r' then | |||
xarg = w | |||
end | |||
-- Add parsed argument to table | |||
table.insert(func_args,xarg) | |||
end | |||
-- Run function | |||
_add.d = func(unpack(func_args)) | |||
end | |||
if _add.d == nil or _add.d == nil_param then | |||
-- have to do pagename defaults here to prevent weird behaviour with switch values | |||
if v == 'name' then | |||
_add.d = pagename | |||
else | |||
_add.d = edit | |||
end | |||
catdata.all_defined = false | |||
else | |||
--_add.d is not nil | |||
catdata.one_defined = true | |||
end | |||
if self.switchfo then | |||
-- Table of switches values and count of them | |||
local _add_switch = {} | |||
local switches = 0 | |||
-- Look for up to the maximum number | |||
for i=1, self.versions do | |||
local _addarg | |||
-- see if this param is allowed to have switch | |||
if v ~= 'image' and v ~= 'examine' and string.find(self.args[v..i] or '','%$%d') then | |||
local refi = string.match(self.args[v..i],'%$(%d+)') | |||
_addarg = _add_switch[tonumber(refi)] or nil_param | |||
else | |||
-- If the value of params is a function | |||
if type(self.params[v]) == 'function' then | |||
-- Perform that function with the parameter at that index | |||
_addarg = self.params[v](self.args[v..i]) | |||
-- If it's a table, parse it into a function | |||
elseif type(self.params[v]) == 'table' then | |||
-- Find the functions name | |||
local func = self.params[v].name | |||
if type(func) == 'string' then | |||
func = func_map[func] | |||
end | |||
-- catch all | |||
if type(func) ~= 'function' then | |||
func = has_content | |||
end | |||
-- Recreate table of args and flags | |||
local func_args = {} | |||
local flag = {} | |||
-- If the flags are NOT a table, turn them into a table | |||
-- Same size as the parameter table | |||
-- Every flag will be the same | |||
if type(self.params[v].flag) ~= 'table' then | |||
-- Map flags, if unmapped, use default | |||
local _flag = flagmap[self.params[v].flag] or 'd' | |||
-- recreate table | |||
for x=1,#self.params[v].params do | |||
table.insert(flag,_flag) | |||
end | |||
-- If flags are already a table, recreate them in new table | |||
elseif type(self.params[v].flag) == 'table' then | |||
local _flag = self.params[v].flag | |||
-- recreate table | |||
for x=1,#self.params[v].params do | |||
-- Map flags, if unmapped, use default | |||
table.insert(flag,flagmap[_flag[x]] or 'd') | |||
end | |||
end | |||
-- Recreate param table, parsing flags as instructions | |||
for x, w in ipairs(self.params[v].params) do | |||
local xarg | |||
-- By default or defined as 'd' | |||
-- looks for the cleaned value of the named parameter first | |||
-- if it doesn't exist, look at the passed value next | |||
-- if that doesn't exist, look at the default | |||
-- if that doesn't exist, use blank | |||
if flag[x] == 'd' then | |||
if self.rargs[w] then | |||
if self.rargs[w].switches then | |||
xarg = self.rargs[w].switches[i] | |||
else | |||
xarg = self.args[w..i] | |||
end | |||
if xarg == nil or xarg == nil_param then | |||
xarg = self.rargs[w].d | |||
end | |||
end | |||
-- multiple catches in a row just to cover everything | |||
if xarg == nil or xarg == nil_param then | |||
xarg = self.args[w..i] | |||
end | |||
if xarg == nil or xarg == edit or xarg == nil_param then | |||
xarg = self.args[w] | |||
end | |||
if xarg == nil or xarg == edit or xarg == nil_param then | |||
xarg = '' | |||
end | |||
-- Look only at the passed value of the named parameter | |||
-- if that doesn't exist, use unnumbered parameter | |||
-- if that doesn't exist, use blank | |||
elseif flag[x] == 'p' then | |||
xarg = self.args[w..i] or self.args[w] or '' | |||
-- Don't interpret value as a parameter name, and paste the as is | |||
elseif flag[x] == 'r' then | |||
xarg = w | |||
end | |||
-- Add parsed argument to table | |||
table.insert(func_args,xarg) | |||
end | |||
-- Run function | |||
_addarg = func(unpack(func_args)) | |||
end | |||
end | |||
-- If not defined, add the nil_param value | |||
-- An actual nil would cause errors in placement | |||
-- So it needs to be filled with an actual value | |||
-- "nil_param" is understood as nil in other functions | |||
-- Include table in case parameter isn't defined by template | |||
if _addarg == nil or _addarg == nil_param then | |||
table.insert(_add_switch, nil_param) | |||
else | |||
switches = switches + 1 | |||
table.insert(_add_switch, _addarg) | |||
catdata.one_defined = true | |||
end | |||
end | |||
-- If there are actually other values to switch to | |||
-- Define a switches subtable, otherwise ignore it | |||
if switches > 0 then | |||
_add.switches = _add_switch | |||
end | |||
end | |||
-- Quick fix for names (which defaults to pagename) | |||
if v == 'name' then | |||
if _add.d == pagename then | |||
if _add.switches and _add.switches[1] ~= pagename and _add.switches[1] ~= nil_param then | |||
_add.d = _add.switches[1] | |||
end | |||
end | |||
end | |||
-- Parameter cleaning finished, add to table of cleaned args | |||
self.rargs[v] = _add | |||
-- Category metadata | |||
-- If every param except default is defined, all_defined = true | |||
if catdata.all_defined == false then | |||
if _add.d == edit then | |||
if _add.switches then | |||
catdata.all_defined = true | |||
for _, v in ipairs(_add.switches) do | |||
if v == nil_param then | |||
catdata.all_defined = false | |||
break | |||
end | |||
end | |||
end | |||
end | |||
end | |||
self.catdata[v] = catdata | |||
end | |||
-- mass dupe removal | |||
-- this needs to be done at the end to keep dependent parameters working | |||
-- also removes incompatible data types | |||
for _, v in ipairs(self.paramnames) do | |||
-- not removed from dupe enabled params parameters | |||
if not self.dupeable[v] and self.rargs[v] and self.rargs[v].switches then | |||
-- tells us whether or not we'll need to remove the switch data | |||
-- switched to false if a switch values does not match the default | |||
local rmvswitch = true | |||
for q, z in ipairs(self.rargs[v].switches) do | |||
-- remove types that don't turn into strings properly | |||
if type(z) == 'table' or type(z) == 'function' then | |||
self.rargs[v].switches[q] = nil_param | |||
-- if it isn't nil or an edit button | |||
-- change variable to keep the switch data | |||
elseif z ~= nil_param and z ~= edit then | |||
rmvswitch = false | |||
end | |||
end | |||
-- remove switch data if everything was a dupe | |||
if rmvswitch then | |||
self.rargs[v].switches = nil | |||
end | |||
end | |||
end | |||
-- Title parentheses (has to be done here, sadly) | |||
local _name | |||
if self.rargs.name then | |||
_name = self.rargs.name.d | |||
-- replace html entities to their actual character | |||
_name = mw.text.decode(_name) | |||
-- if the page name matches the item name, don't add parentheses | |||
if _name == mw.title.getCurrentTitle().fullText then | |||
self.rtable:addClass('no-parenthesis-style') | |||
end | |||
end | |||
end | |||
--[[ | |||
Function to link internal use parameters with JS class attribution | |||
-- self:linkParams{ { arg1, arg2 }, ... { arg1a, arg2a } } | |||
-- arg1: parameter name being linked | |||
-- arg2: parameter name that holds the classes | |||
-- THIS FUNCTION SHOULD BE RUN AFTER :cleanParams() | |||
-- THIS FUNCTION SHOULD BE RUN BEFORE :finish() | |||
-- The second argument's data should always contain a value (a CSS class name) at every index | |||
-- This function is cancelled for non switch boxes | |||
--]] | |||
function Infobox:linkParams(...) | |||
if not self.switchfo then | |||
return | |||
end | |||
for _, v in ipairs(...) do | |||
self.switchfoattr[v[1]] = self.rargs[v[2]] | |||
end | |||
end | |||
--[==========================================[ | |||
-- Functions for accessing parameters easily | |||
--]==========================================] | |||
--[[ | |||
Access the param | |||
-- arg : param name | |||
-- retp : return type | |||
d | #default : self.rargs[arg].d -- Default value | |||
f | full : self.rargs[arg] -- Entire table | |||
s | switches : self.rargs[arg].switches -- Entire switch table | |||
s# : self.rargs[arg].switches[#] -- Single switch value at index # | |||
r : switches[1] or d | |||
--]] | |||
function Infobox:param(arg, retp) | |||
-- All parameters | |||
if arg == 'all' then | |||
return self.rargs | |||
end | |||
-- case-insensitive flagging | |||
retp = tostring(retp):lower() | |||
local fmap = { | |||
d = 'd', default = 'd', s0 = 'd', -- let s-naught count as default (since that's what it is) | |||
f = 'f', full = 'f', | |||
s = 's', switch = 's', switches = 's', | |||
r = 'r' | |||
} | |||
local ret_func | |||
-- quickly see if the parameter is a value greater than 0 | |||
if retp:match('s[1-9]') then | |||
ret_func = 's2' | |||
else | |||
-- Otherwise map it to the correct flag, or the default | |||
ret_func = fmap[retp] or fmap.d | |||
end | |||
-- Fetch parameter | |||
local param = self.rargs[arg] | |||
-- Return nil if no table found | |||
if not param then return nil end | |||
-- Return default | |||
if ret_func == 'd' then | |||
return param.d | |||
end | |||
-- Return full table | |||
if ret_func == 'f' then | |||
return param | |||
end | |||
-- Return switch table | |||
if ret_func == 's' then | |||
return param.switches | |||
end | |||
-- Return the first switch, otherwise the default | |||
if ret_func == 'r' then | |||
if not param.switches then | |||
return param.d | |||
elseif param.switches[1] == nil_param then | |||
return param.d | |||
else | |||
return param.switches[1] | |||
end | |||
end | |||
-- If s2, reread the param | |||
if ret_func == 's2' then | |||
-- no switches | |||
if not param.switches then | |||
return nil | |||
end | |||
-- Parse index by removing the s | |||
local index = retp:match('s(%d+)') | |||
-- nil_param | |||
if param.switches[index] == nil_param then | |||
return nil | |||
else | |||
return param.switches[index] | |||
end | |||
end | |||
end | |||
--[[ | |||
Checks if a parameter is defined and not blank | |||
-- arg : parameter to look at | |||
-- index : index of switches to look at (defaults to default param) | |||
-- defining 'all' will look at every possible value for the parameter | |||
--]] | |||
function Infobox:paramDefined(arg,index) | |||
-- Can use cleaned params for switches | |||
-- but need the passed to identify blanks in the template | |||
local param = self.rargs[arg] | |||
local _arg = self.args[arg] | |||
if string.find(_arg or '','%?action=edit') then | |||
_arg = '' | |||
end | |||
index = index or 0 | |||
local ret | |||
-- create a long strong of every value to test for things if 'all' | |||
if string.lower(index) == 'all' then | |||
return self.catdata[arg] and (self.catdata[arg].one_defined or self.catdata[arg].all_defined) | |||
-- index to number otherwise | |||
else | |||
index = tonumber(index) or 0 | |||
if index == 0 then | |||
if param.switches then | |||
if Infobox.isDefined(param.switches[1]) then | |||
ret = param.switches[1] | |||
else | |||
ret = _arg | |||
end | |||
else | |||
ret = _arg | |||
end | |||
else | |||
if not param.switches then | |||
return nil | |||
end | |||
if param.switches[index] == nil_param then | |||
return nil | |||
end | |||
ret = param.switches[index] | |||
end | |||
end | |||
return tostring(ret or ''):find('%S') | |||
end | |||
--[[ | |||
Function to perform a search on all parameters of a defined name | |||
-- param: param name | |||
-- val: a value or function | |||
-- functions passed must return either true or false | |||
-- with true being counted as a match | |||
--]] | |||
function Infobox:paramGrep(param,val) | |||
local arg = self.rargs[param] | |||
-- if no parameters, return nil | |||
if not arg then | |||
return nil | |||
end | |||
local ret | |||
local valtype = type(val) | |||
-- start with the default value | |||
-- if it's a function, run it | |||
if valtype == 'function' then | |||
ret = val(arg.d) | |||
-- true means it matched | |||
if ret == true then | |||
return ret | |||
end | |||
-- switches up here for functions | |||
if arg.switches then | |||
for _, v in ipairs(arg.switches) do | |||
ret = val(v) | |||
if ret == true then | |||
return true | |||
end | |||
end | |||
end | |||
-- if it's just a value, compare the two | |||
-- only run if types match to avoid errors | |||
-- compare switches later | |||
elseif valtype == type(arg.d) then | |||
-- if a string, make case insensitive | |||
if valtype == 'string' then | |||
if string.lower(val) == string.lower(arg.d) then | |||
return true | |||
end | |||
-- everything else just test directly | |||
elseif val == arg.d then | |||
return true | |||
end | |||
end | |||
-- switch cases | |||
-- more organised putting them here | |||
if arg.switches then | |||
for _, v in ipairs(arg.switches) do | |||
if valtype == type(v) then | |||
if valtype == 'string' then | |||
if val:lower() == v:lower() then | |||
return true | |||
end | |||
elseif val == v then | |||
return true | |||
end | |||
end | |||
end | |||
end | |||
-- return false in every other case | |||
return false | |||
end | |||
------ | |||
function Infobox.paramRead(arg,val) | |||
-- if no parameters, return nil | |||
if not arg then | |||
return nil | |||
end | |||
local ret | |||
local valtype = type(val) | |||
-- start with the default value | |||
-- if it's a function, run it | |||
if valtype == 'function' then | |||
ret = val(arg.d) | |||
-- true means it matched | |||
if ret == true then | |||
return ret | |||
end | |||
-- switches up here for functions | |||
if arg.switches then | |||
for _, v in ipairs(arg.switches) do | |||
ret = val(v) | |||
if ret == true then | |||
return true | |||
end | |||
end | |||
end | |||
-- if it's just a value, compare the two | |||
-- only run if types match to avoid errors | |||
-- compare switches later | |||
elseif valtype == type(arg.d) then | |||
-- if a string, make case insensitive | |||
if valtype == 'string' then | |||
if string.lower(val) == string.lower(arg.d) then | |||
return true | |||
end | |||
-- everything else just test directly | |||
elseif val == arg.d then | |||
return true | |||
end | |||
end | |||
-- switch cases | |||
-- more organised putting them here | |||
if arg.switches then | |||
for _, v in ipairs(arg.switches) do | |||
if valtype == type(v) then | |||
if valtype == 'string' then | |||
if val:lower() == v:lower() then | |||
return true | |||
end | |||
elseif val == v then | |||
return true | |||
end | |||
end | |||
end | |||
end | |||
-- return false in every other case | |||
return false | |||
end | |||
---- | |||
-- Return collected category data | |||
function Infobox:categoryData() | |||
return self.catdata | |||
end | |||
-- Infobox:addDropLevelVars("thieving", "skilllvl1") | |||
function Infobox:addDropLevelVars(key, paramName) | |||
local levelParams = self:param(paramName, 'f') | |||
local dropParams = self:param('dropversion', 'f') | |||
if levelParams == nil then | |||
return | |||
end | |||
if dropParams == nil or not self:paramDefined("dropversion", "all") then | |||
dropParams = {d = 'DEFAULT'} | |||
end | |||
if dropParams.switches == nil then | |||
dropParams.switches = {} | |||
end | |||
local levels = levelParams.switches or {levelParams.d} | |||
local dropVersions = {} | |||
for i=1,#levels do | |||
local dropVersionFromInfobox = dropParams.switches[i] or dropParams.d | |||
if dropVersionFromInfobox == nil_param then | |||
dropVersionFromInfobox = 'DEFAULT' | |||
else | |||
dropVersionFromInfobox = dropVersionFromInfobox .. ",DEFAULT" | |||
end | |||
for dropVersion in string.gmatch(dropVersionFromInfobox, ' *([^,]+) *') do | |||
if dropVersions[dropVersion] == nil then | |||
dropVersions[dropVersion] = {} | |||
end | |||
dropVersions[dropVersion][levels[i]] = true | |||
end | |||
end | |||
-- This part is to append levels from previous Infobox invocations | |||
for dropVersion, dropLevels in pairs(dropVersions) do | |||
-- 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 v in string.gmatch(previousVar, ' *([^,]+) *') do | |||
dropVersions[dropVersion][v] = true | |||
end | |||
end | |||
local ordered = {} | |||
for k, v in pairs(dropVersions[dropVersion]) do | |||
local n = tonumber(k) | |||
if n ~= nil then | |||
table.insert(ordered, n) | |||
end | |||
end | |||
table.sort(ordered) | |||
var.vardefine(var_name, table.concat(ordered, ',')) | |||
end | |||
end | |||
-- Override tostring | |||
function Infobox.tostring(box) | |||
-- If not finished, finish | |||
if not box.__finished then | |||
box:finish() | |||
end | |||
-- Make entire html wrapper a string and return it | |||
local btns = box.switch_buttons_tag | |||
if box.custom_buttons then | |||
btns = '' | |||
end | |||
if box.args.__dump__ then | |||
return '<' .. 'pre>'..mw.dumpObject(box) .. '</' .. 'pre>[[Category:Dumping infoboxes]]' | |||
end | |||
return tostring(btns) .. tostring(box.rtable) .. table.concat(box.appendStrs, '') .. tostring(box.switch_tag) .. tostring(box.smw_error_tag) | |||
end | |||
return Infobox | |||
-- </nowiki> |
Latest revision as of 15:52, 30 March 2024
Documentation for this module may be created at Module:Infobox/doc
--[=[
-- For documentation, see [[Module:Infobox/doc]]
--]=]
-- <nowiki>
local Infobox = {}
Infobox.__index = Infobox
Infobox.__tostring = Infobox.tostring
-- Edit button for unknown params
local editbutton = require('Module:Edit button')
local var = mw.ext.VariablesLua
local edit = editbutton("'''?''' (edit)")
-- Page title
local pagename = mw.title.getCurrentTitle().fullText
-- map of flags to html tags used by Infobox.addRow()
-- let's only define it once, since :addRow() is used multiple times per module
local tagmap = {
tr = 'tr',
th = 'th',
td = 'td',
argh = 'th',
argd = 'td'
}
--[=[
-- Standardised functions
-- called as string with defineParams
--]=]
-- Standardised "has content" function
function hasContent(arg, default)
-- Return arg if any non-whitespace character is found
return string.match(arg or '','%S') and arg or default
end
-- Standardised "name" function
function subjectName(arg)
return string.match(arg or '','%S') and arg or nil
end
-- Create a standardised release function, since so many pages use it
-- Turns release and update into a single parameter
function releaseUpdate(release, update)
if not Infobox.isDefined(release) then
return nil
end
if string.lower(release) == 'no' then
return 'N/A'
end
if not Infobox.isDefined(update) then
return string.format('%s (Update unknown)',release)
end
if string.lower(update) == 'no' then
return release
end
return string.format('%s ([[Update:%s|Update]])', release, update)
end
-- Standardised image function
function image(img)
if img and img:find('%S') then
return img
else
return nil
end
end
-- Standardised numbers
function numbers(num)
num = string.gsub(num or '',',','')
return tonumber(num)
end
-- Wrap content with line breaks if it contains list-like wiki syntax
function wrapContent(content)
if type(content) == "string" then
local firstListItem = math.min(content:find('^[*#]') or math.huge, content:find('\n[*#]') or math.huge)
if firstListItem ~= math.huge then
local suffix = content:find('\n[*#][^\n]+$') and '\n' or ''
content = content:sub(1, firstListItem - 1) .. '\n' .. content:sub(firstListItem) .. suffix
end
end
return content
end
-- map of names to pre-defined functions, used by Infobox:defineParams
local func_map = {
name = subjectName,
release = { name = releaseUpdate, params = { 'release', 'update' }, flag = 'p' },
removal = { name = releaseUpdate, params = { 'removal', 'removalupdate' }, flag = 'p' },
has_content = hasContent,
hasContent = hasContent,
image = image,
numbers = numbers,
}
-- used to fill nil params in switching sections
-- this message isn't kidding
-- If you see this message anywhere outside of this code
-- (including inside switchfo box data)
-- report it
local nil_param = 'UH OH YOU SHOULDN\'T SEE THIS!'
-- In case the nil_param is needed outside of this module
-- give it an easy way to be accessed
function Infobox.nilParam()
return nil_param
end
-- switch infobox globals
local LINE_WIDTH = 300
local MAX_LINES = 2
local DEFAULT_MAX_BUTTONS = 6
-- calculate with width of a switch infobox button
-- potential @TODO: rework to use actual character widths
function button_width(label)
local PX_PER_CHAR = 6
local PX_PAD_MAR = 24
return string.len(label) * PX_PER_CHAR + PX_PAD_MAR
end
Infobox.splitpoint = '&&SPLITPOINT&&'
-- quick test to see if a value is considered nil
function Infobox.isDefined(arg)
if arg == nil then
return false
end
if type(arg) == 'string' then
if arg == nil_param then
return false
elseif arg:find('%S') then
if arg:find('action=edit') then
return false
else
return true
end
else
return false
end
end
return true
end
--[[
Infobox class
-- args : parameters from frame to pass through
-- Sets a meta table and creates a <div> tag wrapper
-- other fields are initialised in other functions
--]]
function Infobox.new(args)
local obj = setmetatable({
args = args, -- parameters (uncleaned)
rargs = {}, -- parameters (cleaned)
params = {}, -- parameters mapped to functions
paramnames = {}, -- parameter names
dupeable = {}, -- parameters that are allowed to have duplicated switch data
addrswibclass = true,
switchfo = false, -- switch infobox? or not?
switchfoattr = {}, -- switch data class changes
maxbuttons = DEFAULT_MAX_BUTTONS, -- maximum number of buttons before switching becomes a menu
switch_tag = '', -- switchfo data
switch_buttons_tag = '', -- switchfo buttons
custom_buttons = false,
smw_error_tag = '',
rtable = nil, -- returned infobox table
labels = nil, -- returned labels
_smw = {}, -- semantic mediawiki data
_smwOne = {}, -- semantic mediawiki data part 2
_smwSubobject = {}, -- semantic mediawiki data part 3
_smwSubobjectName = nil, -- semantic mediawiki data part 3.5
_smwElement = {}, -- semantic mediawiki data part 4
set_default_version_smw = false, -- whether to set [[Property:Default version]]
setSMWElement = true,
suppressAllSMW = false,
suppressVersionSMW = {},
versions = -1, -- number of switch versions (-1 is uncalculated)
infoboxname = nil, -- template name
appendStrs = {},
bottomlinks = { -- template bottom links
links = {
{ 'Template talk:%s', 'talk' },
{ 'Template:%s', 'view' }
},
colspan = 2
},
catdata = {}, -- meta category data
catlist = {}, -- defined table of category names (strings)
__finished = false, -- infobox status
},
Infobox)
return obj
end
--[[
Toggles the addition of infobox class
use before :create()
noop if not a boolean
--]]
function Infobox:setAddRSWInfoboxClass(bool)
if type(bool) == 'boolean' then
self.addrswibclass = bool
end
end
--[[
Creates an infobox
-- If Infobox:maxVersions() has not been run, it will be run here
-- If the infobox should be a switch infobox, all labels will be added
-- Creates a wikitable that will be the infobox
THIS SHOULD BE DONE AFTER ADDING AND CLEANING PARAMETERS
--]]
function Infobox:create()
-- Run to find if this is a switch infobox and if so, how many boxes
if self.versions == -1 then
self:maxVersion()
end
-- Run if switch infobox
if self.switchfo then
-- Buttons wrapper
-- Hidden by default, unhidden by javascript
self.switch_buttons_tag = mw.html.create('div')
:addClass('infobox-buttons')
-- default version to immediately switch to via js
local defv = tonumber(self.args.defver)
if defv and defv <= self.versions then -- you troll, don't try to show something that isn't there
self.switch_buttons_tag:attr('data-default-version',defv)
end
local numlines = 1
local width_working = 0
local total_width = 0
local buttons = {}
-- Add individual buttons to the wrapper
for i=1,self.versions do
local wid = button_width(self.labels[i] or i)
width_working = width_working + wid
total_width = total_width + wid
if width_working > LINE_WIDTH then
numlines = numlines + 1
width_working = wid
end
local b = mw.html.create('span')
:attr('data-switch-index',tostring(i))
-- space to underscore
:attr('data-switch-anchor','#'..string.gsub(self.labels[i] or i,' ','_'))
:addClass('button')
:wikitext(self.labels[i] or i)
table.insert(buttons, {b, wid})
end
local best = {-1, 100000}
if (numlines > 1) and (numlines <= MAX_LINES) then
-- attempt to balance line widths
local w_s, w_e = 0,total_width
for i = 1,#buttons-1 do
w_s = w_s + buttons[i][2]
w_e = w_e - buttons[i][2]
if w_s > LINE_WIDTH then
-- w_s only increases, so we're done once it exceeds the width
break
end
if w_e <= LINE_WIDTH then
-- w_e only decreases, so just continue if it exceeds line
local diff = math.abs(w_s - w_e)
if diff < best[2] then
best = { i, diff }
end
end
end
if best[1] == -1 then
best = { math.floor(#buttons/2), 100000 }
end
end
for i,v in ipairs(buttons) do
self.switch_buttons_tag:node(v[1])
if i == best[1] then
self.switch_buttons_tag:tag('span'):addClass('line-break')
end
end
-- Used by JavaScript to turn the buttons into a menu list if too many variants
if self.versions > self.maxbuttons or numlines > MAX_LINES then
self.switch_buttons_tag:addClass('infobox-buttons-select')
end
self.switch_buttons_tag:done()
end
-- Create infobox table
self.rtable = mw.html.create('table')
if self.addrswibclass then
self.rtable:addClass('infobox')
end
-- Add necessary class if switch infobox
if self.switchfo then
self.rtable:addClass('infobox-switch')
end
end
-- Defines an infobox name ({{Template:arg}})
-- Used to create a link at the bottom of pages
function Infobox:defineName(arg)
self.infoboxname = arg
end
-- Defines the bottom links of the infobox
-- pass a table whose elements are tables that define a link and a label
-- {
-- { 'link', 'label },
-- ...
-- }
-- The template name can be substituted into the tables using '%s'
-- If we wanted Template:InFooBar to link to it's /doc page with a "doc" label:
-- { ...
-- { 'Template:%s/doc', 'doc' },
-- ... }
-- The template's name can only be called 5 times
function Infobox:defineLinks(arg)
if type(arg) == 'table' then
if arg.colspan then
self.bottomlinks.colspan = arg.colspan
end
if arg.links then
if type(arg.links) == 'table' then
self.bottomlinks.links = arg.links
end
end
if arg.hide then
self.bottomlinks.hide = arg.hide
end
end
end
-- Change max number of buttons before switching to menu
-- defaults to 5
-- MUST BE RUN BEFORE :create()
function Infobox:setMaxButtons(arg)
-- if not a number, just go back to default
self.maxbuttons = tonumber(arg) or DEFAULT_MAX_BUTTONS
end
--[[
Add parameters functions
All parameters should be tables
The first parameter defines the type of cell to create
-- th : <th>
-- td : <td>
-- argh : <th>
-- argd : <td>
The second parameter defines what is inside the tag
-- th | th : text passed
-- argh | argd : parameter with the name passed
Additional named parameters can be used to add any styling or attributes
-- attr : mw.html:attr({ arg1 = '1', ... })
-- css : mw.html:css({ arg1 = '1', ...)
-- class : mw.html:addClass('arg')
---- class also supports a table of values, even though mw.html:addClass() does not
-- rowspan : mw.html:attr('rowspan',arg)
-- colspan : mw.html:attr('colspan',arg)
-- title : mw.html:attr('title',arg)
Example:
ipsobox:addRow( { 'th' , 'Header', title = 'Title' },
{ 'argh', 'arg1', class = 'parameter' } })
produces:
<tr><th title="Title">Header</th><th class="parameter">args.arg1</th></tr>
adding it to the infobox table of ipsobox
Cells defined as 'argh' and 'argd' will automatically have data-attr-param="" added, and defined as the passed argument if the infobox in creation is defined as a switch infobox
The row itself may be modified with metadata using the named index at "meta"
-- meta.addClass : mw.html:addClass('arg')
-- this function currently only supports a single string
--]]
function Infobox.addRow(box, ...)
-- New row to add
local args = ...
local _row = box.rtable:tag('tr')
-- For each member of tags
for i, v in ipairs(args) do
-- map tag name to appropriate tag, default to <td>
local _cell = _row:tag(tagmap[v.tag] or 'td')
-- mw.html:attr() and mw.html:css() both accept table input
-- colspan, rowspan, title will be quick ways to access attr
-- these functions also do all the necessary work
if v.attr then
_cell:attr(v.attr)
end
if v.colspan then
_cell:attr('colspan',v.colspan)
end
if v.rowspan then
_cell:attr('rowspan',v.rowspan)
end
if v.title then
_cell:attr('title',v.title)
end
if v.css then
_cell:css(v.css)
end
-- if class is a string, it can be added directly
-- if a table, add every value
-- mw.html:addClass() doesn't function with tables
-- so iterate over the class names here and add them individually
if v.class then
if type(v.class) == 'string' then
_cell:addClass(v.class)
elseif type(v.class) == 'table' then
for _, w in ipairs(v.class) do
_cell:addClass(w)
end
end
end
-- if the cell is a normal th or td, add the exact argument passed
if v.tag == 'th' or v.tag == 'td' then
_cell:wikitext(wrapContent(v.content))
-- if defined with "arg", add the argument with name passed
elseif v.tag == 'argh' or v.tag == 'argd' then
local content = box.rargs[v.content]
-- if the requested parameter doesn't exist whatsoever, just return a blank string
if not content then
content = ''
-- If switches exist, first attempt to use the version1 values
elseif content.switches then
if content.switches[1] ~= nil_param then
content = content.switches[1] or ''
else
content = content.d or ''
end
-- fallback to default value
else
content = content.d or ''
end
_cell:wikitext(wrapContent(content))
-- add necessary attribute for switch infoboxes
if box.switchfo then
_cell:attr('data-attr-param',v.content)
end
end
end
-- not that meta
-- allow classes to be defined on the whole row
-- okay, sort of meta
if args.meta then
if args.meta.addClass then
_row:addClass(args.meta.addClass)
end
end
return box
end
function Infobox.customButtonPlacement(box,arg)
box.custom_buttons = arg
return box
end
-- Choose whether to set [[Property:Default version]]
-- Defaults to false.
function Infobox.setDefaultVersionSMW(box, arg)
box.set_default_version_smw = arg
return box
end
function Infobox.addButtonsRow(box, args)
if box.switchfo then
box.custom_buttons = true
local _row = box.rtable:tag('tr')
:addClass('infobox-switch-buttons-row')
:tag('td')
:addClass('infobox-switch-buttons')
:attr('colspan', args.colspan)
:node(box.switch_buttons_tag)
end
return box
end
function Infobox.addButtonsCaption(box)
if box.switchfo then
box.custom_buttons = true
local _row = box.rtable:tag('caption')
:addClass('infobox-switch-buttons-caption')
:node(box.switch_buttons_tag)
end
return box
end
--[[
-- adds a blank row of padding spanning the given number of columns
--]]
function Infobox.pad(box, colspan, class)
local tr = box:tag('tr')
:tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding')
:done()
if class then
tr:addClass(class)
end
tr:done()
return box
end
--[[
-- functions the same as mw.html:wikitext() on the wrapper
-- Should only be used for categories really
--]]
function Infobox.wikitext(box, arg)
box.rtable:wikitext(arg)
return box
end
--[[
-- Adds the specified item(s) to the end of the infobox, outside of the table
-- items are concatenated together with an empty space
--]]
function Infobox.append(box, ...)
for i,v in ipairs({...}) do
table.insert(box.appendStrs, v)
end
return box
end
--[[
-- Adds a caption to the infobox
-- defaults to the pagename
-- or the default argument if defined
--]]
function Infobox.caption(box)
-- default to the article's name
local name = pagename
-- first see if the name parameter exists
if box.rargs.name then
-- then try the default
if box.rargs.name.d then
name = box.rargs.name.d
-- then look for swithes
elseif box.rargs.name.switches then
-- then look at version 1
if box.rargs.name.switches[1] ~= nil_param then
name = box.rargs.name.switches[1]
end
end
end
local caption = box.rtable:tag('caption')
:wikitext(name)
-- add necessary attribute for switch infoboxes
if box.switchfo then
caption:attr('data-attr-param','name')
end
return box
end
--[[
-- Functions for styling the infobox
-- works the same as the respective mw.html functions
--]]
-- attr
function Infobox.attr(box, arg)
box.rtable:attr(arg)
return box
end
-- css
function Infobox.float(box,float)
box.rtable:css('float',float)
return box
end
function Infobox.css(box, ...)
box.rtable:css(...)
return box
end
-- addClass
function Infobox.addClass(box, arg)
box.rtable:addClass(arg)
return box
end
-- Much like Infobox.addClass, but adds multiple classes
function Infobox.addClasses(box, ...)
for _, v in ipairs(...) do
box.rtable:addClass(box)
end
return box
end
--[[
Add tags directly to the infobox table
Use sparingly
Returns the tag created rather than the entire box
Which is an mw.html object
Further uses of :tag() will be mw.html.tag, rather than Infobox.tag
As such, Infobox:addRow() cannot be used afterwards without restating the infobox as the object
--]]
function Infobox.tag(box, arg)
return box.rtable:tag(arg)
end
--[[
Allows the infobox to use Semantic Media Wiki and give parameters properties
Pass a table to this function to map parameter names to properties
Calling syntax:
-- {{#show:page|?property}}:
-- "<property>" - unqualified and without a number will display the default value
-- "<property#>" - with a number will show the switch data from that index
-- "all <property>" - adding all will display every unique value in a comma separated list
Properties initiated in Infobox:finish()
--]]
function Infobox:useSMW(arg)
if type(arg) == 'table' then
for w, v in pairs(arg) do
self._smw[w] = v
end
end
end
--[[
As above, but only assigns to "<property>", which will act like "all <property>" - "<property>#" not present
Properties initiated in Infobox:finish()
--]]
function Infobox:useSMWOne(arg)
if type(arg) == 'table' then
for w, v in pairs(arg) do
self._smwOne[w] = v
end
end
end
--[[
Set up the infobox to set properties in a SMW subobject. This will create a subobject for each version
- if there is only one version, it will put the properties directly on to the page, like useSMWOne
Properties initiated in Infobox:finish()
--]]
function Infobox:useSMWSubobject(arg)
if type(arg) == 'table' then
for w, v in pairs(arg) do
self._smwSubobject[w] = v
end
end
end
function Infobox:useSMWElement(arg)
if type(arg) == 'table' then
for w, v in pairs(arg) do
self._smwElement[w] = v
end
self.setSMWElement = true
end
end
--[[
Finishing function
-- Finishes the return, adding necessary final tags
--]]
function Infobox:finish()
local currentNamespace = mw.title.getCurrentTitle().namespace
-- 0 = mainspace, 4 = RuneScape
local onContentNamespace = currentNamespace == 0 or currentNamespace == 4
-- Don't finish twice
if self.__finished then
return
end
-- Add switch infobox resources
if self.switchfo then
self.rtable:attr('data-resource-class', '.infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_'))
-- Wrapper tag, hidden
self.switch_tag = mw.html.create('div')
:addClass('infobox-switch-resources')
:addClass('infobox-resources-'..string.gsub(tostring(self.infoboxname), ' ', '_'))
:addClass('hidden')
for _, v in ipairs(self.paramnames) do
local param = self.rargs[v]
local default_value = param.d or edit
-- Parameters may not have any switches data, those are ignored
local switchattr = self.switchfoattr[v]
-- Parameter data wrapper
local res_span = self.switch_tag:tag('span')
:attr('data-attr-param',v)
-- Child for default value
local def = res_span:tag('span')
:attr('data-attr-index',0)
:wikitext(tostring(default_value))
-- Switch classes
if switchattr then
def:attr('data-addclass',switchattr.d)
end
def:done()
if param.switches then
-- Add all switches, ignore those defined as nil
for i, w in ipairs(param.switches) do
if w ~= nil_param and w ~= nil and w ~= default_value then
local _w = res_span:tag('span')
:attr('data-attr-index',i)
:wikitext(tostring(w))
-- Switch classes
if switchattr and switchattr.switches then
_w:attr('data-addclass',switchattr.switches[i])
elseif switchattr then
mw.logObject({string.format("Expected switches for `%s` but got none:", v), switchattr})
end
_w:done()
end
end
res_span:done()
end
end
-- Add a tracking category for pages that have more than 1 version
if onContentNamespace and self.versions > 1 then
-- version count data
self.switch_tag:wikitext('[[Category:Pages that contain switch infobox data]]')
if not self.suppressAllSMW then
self.switch_tag:tag('span')
:wikitext(string.format('Versions: [[Version count::%s]]',self.versions))
:done()
-- set default version smw
local defver = tonumber(self.args.defver) or 1
local default_subobject_value = self.args['smwname'..defver] or self.args['version'..defver]
if default_subobject_value and self.set_default_version_smw then
-- Only take first subobject if multiple are defined using "¦"
local default_subobject_name = default_subobject_value:match("[^¦]+")
self.switch_tag:tag('span')
:wikitext(string.format('Default version: [[Default version::%s]]',default_subobject_name))
end
end
end
self.switch_tag:done()
end
-- smw data
if onContentNamespace and not self.suppressAllSMW then
-- members smw display, yes --> true; no --> false; other --> unknown
local function smwMembers(smw_arg)
local smw_argv = string.lower(smw_arg or '')
if smw_argv == 'yes' then
return 'true'
elseif smw_argv == 'no' then
return 'false'
else
return 'unknown'
end
end
-- release date smw display
local function smwRelease(smw_arg)
local _d,_m,_y = string.match(smw_arg or '', '%[%[(%d%d?) (%a+)%]%] %[%[(%d%d%d%d)%]%]')
if _d == nil then
return nil
end
return table.concat({_d,_m,_y},' ')
end
-- default, just return the text
local function smwDefault(smw_arg)
if smw_arg ~= nil_param and smw_arg ~= edit then
return smw_arg
else
return 'unknown'
end
end
local smw_to_func = {
members = smwMembers,
release = smwRelease,
removal = smwRelease,
default = smwDefault
}
local smw_data_arr = {}
-- custom properties
for w, v in pairs(self._smw) do
-- only needed to give special formatting to release
-- and to make members true/false
local smwfunc = smw_to_func[w] or smw_to_func.default
local curarg = self.rargs[w]
if curarg then
local _arg = curarg.d
local argdefault = _arg
if _arg == edit then
argdefault = 'unknown'
else
local _x = mw.text.split(tostring(_arg), Infobox.splitpoint, true)
if not smw_data_arr[v] then
smw_data_arr[v] = {}
end
if not smw_data_arr['All '..v] then
smw_data_arr['All '..v] = {}
end
for _,y in ipairs(_x) do
local temp_smw_data = smwfunc(y)
table.insert(smw_data_arr[v], temp_smw_data)
table.insert(smw_data_arr['All '..v], temp_smw_data)
end
end
if curarg.switches then
local _args = {}
for x_i, x in ipairs(curarg.switches) do
if not self.suppressVersionSMW[x_i] then
if x ~= nil_param then
table.insert(_args,x)
else
table.insert(_args,argdefault or nil_param)
end
end
end
for i, x in ipairs(_args) do
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true)
if not smw_data_arr[v..i] then
smw_data_arr[v..i] = {}
end
if not smw_data_arr['All '..v] then
smw_data_arr['All '..v] = {}
end
for _,y in ipairs(_x) do
local temp_smw_data = smwfunc(y)
table.insert(smw_data_arr[v..i], temp_smw_data)
table.insert(smw_data_arr['All '..v], temp_smw_data)
end
end
end
end
end
-- if one version, put smwSubobject into smwOne and just do that
if self.versions < 2 and not self._smwSubobjectName then
for w,v in pairs(self._smwSubobject) do
if not self._smwOne[w] then
self._smwOne[w] = v
elseif type(self._smwOne[w]) == 'table' then
table.insert(self._smwOne[w], v)
else
self._smwOne[w] = { self._smwOne[w], v }
end
end
end
for w, _v in pairs(self._smwOne) do
-- only needed to give special formatting to release
-- and to make members true/false
local smwfunc = smw_to_func[w] or smw_to_func.default
local curarg = self.rargs[w]
if curarg then
local _arg = curarg.d
local argdefault = _arg
if _arg == edit then
argdefault = 'unknown'
end
if curarg.switches then
local _args = {}
for x_i, x in ipairs(curarg.switches) do
if not self.suppressVersionSMW[x_i] then
if x ~= nil_param then
table.insert(_args,x)
else
table.insert(_args,argdefault or nil_param)
end
end
end
for i, x in ipairs(_args) do
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true)
for _,y in ipairs(_x) do
local temp_smw_data = smwfunc(y)
for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do
if not smw_data_arr[v] then
smw_data_arr[v] = {}
end
table.insert(smw_data_arr[v], temp_smw_data)
end
end
end
else
if Infobox.isDefined(_arg) then
local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true)
for _,y in ipairs(_targ) do
local temp_smw_data = smwfunc(y)
for _,v in ipairs(type(_v) == 'table' and _v or {_v}) do
if not smw_data_arr[v] then
smw_data_arr[v] = {}
end
table.insert(smw_data_arr[v], temp_smw_data)
end
end
end
end
end
end
local smw_data_arr_elem = {}
for w, v in pairs(self._smwElement) do
-- only needed to give special formatting to release
-- and to make members true/false
local smwfunc = smw_to_func[w] or smw_to_func.default
local curarg = self.rargs[w]
if curarg then
local _arg = curarg.d
local argdefault = _arg
if _arg == edit then
argdefault = 'unknown'
end
if curarg.switches then
local _args = {}
for x_i, x in ipairs(curarg.switches) do
if not self.suppressVersionSMW[x_i] then
if x ~= nil_param then
table.insert(_args,x)
else
table.insert(_args,argdefault or nil_param)
end
end
end
for i, x in ipairs(_args) do
if Infobox.isDefined(x) then
local _x = mw.text.split(tostring(x), Infobox.splitpoint, true)
for _,y in ipairs(_x) do
local temp_smw_data = smwfunc(y)
if not smw_data_arr_elem[v] then
smw_data_arr_elem[v] = {}
end
table.insert(smw_data_arr_elem[v], temp_smw_data)
end
end
end
else
if Infobox.isDefined(_arg) then
local _targ = mw.text.split(tostring(_arg), Infobox.splitpoint, true)
for _,y in ipairs(_targ) do
local temp_smw_data = smwfunc(y)
if not smw_data_arr_elem[v] then
smw_data_arr_elem[v] = {}
end
table.insert(smw_data_arr_elem[v], temp_smw_data)
end
end
end
end
end
-- if is a switchfo, setup for subobjects
local smw_data_arr_subobj = {}
if self._smwSubobjectName then
for i,k in ipairs(self._smwSubobjectName) do
if not self.suppressVersionSMW[i] then
local subobj_data = {
['Is variant of'] = {pagename},
}
for w,v in pairs(self._smwSubobject) do
local smwfunc = smw_to_func[w] or smw_to_func.default
local curarg = self.rargs[w]
if curarg then
local argval = curarg.d
if curarg.switches then
argval = curarg.switches[i]
if not Infobox.isDefined(argval) then
argval = curarg.d
end
end
if Infobox.isDefined(argval) then
local _x = mw.text.split(tostring(argval), Infobox.splitpoint, true)
for _, _x1 in ipairs(_x) do
_x1 = smwfunc(_x1)
if _x1 ~= 'unknown' then
if not subobj_data[v] then
subobj_data[v] = {}
end
table.insert(subobj_data[v], _x1)
end
end
end
end
end
for w,v in ipairs(k) do
local subobj_name = v
-- can't have a . in the first 5 characters of a subobject identifier
if mw.ustring.find(mw.ustring.sub(subobj_name,0,5), '%.') then
self:wikitext('[[Category:Pages with an invalid subobject name]]')
subobj_name = mw.ustring.gsub(subobj_name, '%.', '')
end
if subobj_name == '0' or subobj_name == '' then
self:wikitext('[[Category:Pages with an invalid subobject name]]')
subobj_name = 'v_'..subobj_name
end
smw_data_arr_subobj[subobj_name] = subobj_data
end
end
end
end
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
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
end
end
end
end
if self._smwSubobjectName then
if self.smw_error_tag == '' then
self.smw_error_tag = mw.html.create('div'):css('display','none'):addClass('infobox-smw-data')
end
for w,v in pairs(smw_data_arr_subobj) do
local subobjdiv = self.smw_error_tag:tag('div')
subobjdiv:tag('span'):wikitext('SMW Subobject for '..w)
for j,k in pairs(v) do
subobjdiv:tag('span'):wikitext(mw.ustring.format('%s: %s', j, table.concat(k, ', ')))
end
end
end
end
-- Add view and talk links to infobox
-- Only done if a name is defined
if self.infoboxname and not self.bottomlinks.hide then
local bottom_links = {}
for _, v in ipairs(self.bottomlinks.links) do
table.insert(bottom_links,
string.format(
table.concat({'[[',
v[1],
'|',
v[2],
']]'}),
self.infoboxname,
self.infoboxname,
self.infoboxname,
self.infoboxname,
self.infoboxname)
)
end
bottom_links = table.concat(bottom_links,' • ')
self.rtable:tag('tr'):tag('td')
:addClass('infobox-template-links')
:attr('colspan', self.bottomlinks.colspan)
:wikitext(bottom_links)
:done()
end
-- Define as finished
self.__finished = true
end
--[[
Function for defining parameters
-- name : parameter name
-- func : function to define param, defaults to looking at blanks
DO NOT DEFINE VERSION HERE
USE :maxVersion()
Can be used any number of times for efficient definition
--]]
function Infobox:defineParams(...)
for _, v in ipairs(...) do
-- For every parameter, store its corresponding function to self.params
if v.name then
-- If the value is a function or a table (which should define a function)
if type(v.func) == 'function' or type(v.func) == 'table' then
self.params[v.name] = v.func
-- If the value is a string, use the predefined Infobox function of that name
elseif type(v.func) == 'string' then
self.params[v.name] = func_map[v.func] or hasContent
-- Everything else just looks for blanks
else
self.params[v.name] = hasContent()
end
-- Create a list of all param names
table.insert(self.paramnames,v.name)
-- function to allow duplicated values
if v.dupes then
self.dupeable[v.name] = true
end
end
end
end
--[[
-- Forces an infobox to only use 1 variant
-- Mainly used by lite infoboxes
-- This should be run before creation
--]]
function Infobox:noSwitch()
self.versions = 1
self.switchfo = false
end
--[[
-- Calculates the max version
-- Adds labels
-- Sees if this needs to be a switch infobox
-- Returns extra version count (even if already run)
--]]
function Infobox.maxVersion(box)
-- Only allowed to run once
if box.versions ~= -1 then
return box.versions
end
box.labels = {}
box.versions = 0
local smwname = {}
if string.lower(box.args['smw'] or '') == 'no' then
box.suppressAllSMW = true
end
-- Look for up to 125 variants, defined in order
for i=1, 125 do
-- If variant# exists
if box.args['version'..i] then
-- Increase version count
box.versions = box.versions + 1
-- Add its label
local label = box.args['version'..i] or ('Version '..i)
table.insert(box.labels,label)
-- add to smwname
if box.args['smwname'..i] then
table.insert(smwname, mw.text.split(box.args['smwname'..i], '¦'))
else
table.insert(smwname, {label})
end
box.suppressVersionSMW[i] = (box.args['smw'..i] and string.lower(box.args['smw'..i] or '') == 'no')
else
-- Stop if it doesn't exist
break
end
end
-- Define self as a switch infobox if at least 1 other version is found
if box.versions > 0 then
box.switchfo = true
box._smwSubobjectName = smwname
else
-- single version, check for smwname
if box.args['smwname'] then
box._smwSubobjectName = {mw.text.split(box.args['smwname'], '¦')}
end
end
-- versions calculated
return box.versions
end
--[[
-- Cleans parameters as defined by the above function
-- SHOULD NOT BE RUN UNTIL ALL THINGS ARE DEFINED
-- Handles switches as well
-- adds table _add to rargs, a cleaned up version of arguments
-- d : default value
-- switches : table of switches (in numerical order)
-- Functions can be defined with tables
---- name : name of function
---- params : table of args to pass to functions
---- flag : flags for input
d | #default : use the cleaned parameter first, otherwise passed
p : use the passed value of parameters
r | l : use raw (literal) text, rather than values
-- Defining a single flag will use that flag on all parameters
-- Defining a table of flags will use the respective flag by position
--]]
function Infobox:cleanParams()
-- map of flags to functionality
local flagmap = {
r = 'r',
l = 'r',
d = 'd',
p = 'p'
}
-- For all parameters named
for _, v in ipairs(self.paramnames) do
-- Parameter to add
local _add = {}
local catdata = { all_defined = true, one_defined = false }
-- If the value of params is a function
if type(self.params[v]) == 'function' then
-- Perform that function with the parameter
_add.d = self.params[v](self.args[v])
-- If it's a table, parse it into a function
elseif type(self.params[v]) == 'table' then
-- Find the functions name
local func = self.params[v].name
if type(func) == 'string' then
func = func_map[func]
end
-- catch all
if type(func) ~= 'function' then
func = has_content
end
-- Recreate table of args and flags
local func_args = {}
local flag = {}
-- If the flags are NOT a table, turn them into a table
-- Same size as the parameter table
-- Every flag will be the same
if type(self.params[v].flag) ~= 'table' then
-- Map flags, if unmapped, use default
local _flag = flagmap[self.params[v].flag] or 'd'
-- recreate table
for x=1,#self.params[v].params do
table.insert(flag,_flag)
end
-- If flags are already a table, recreate them in new table
elseif type(self.params[v].flag) == 'table' then
local _flag = self.params[v].flag
-- recreate table
for x=1,#self.params[v].params do
-- Map flags, if unmapped, use default
table.insert(flag,flagmap[_flag[x]] or 'd')
end
end
-- Recreate param table, parsing flags as instructions
for x, w in ipairs(self.params[v].params) do
local xarg
-- By default or defined as 'd'
-- looks for the cleaned value of the named parameter first
-- if it doesn't exist, look at the passed value next
-- if that doesn't exist, use blank
if flag[x] == 'd' then
xarg = self.rargs[w] and self.rargs[w].d
-- compare to nil explicitly because false is a valid value
if xarg == nil then
xarg = self.args[w] or ''
end
-- Look only at the passed value of the named parameter
-- if that doesn't exist, use blank
elseif flag[x] == 'p' then
xarg = self.args[w] or ''
-- Don't interpret value as a parameter name, and paste the as is
elseif flag[x] == 'r' then
xarg = w
end
-- Add parsed argument to table
table.insert(func_args,xarg)
end
-- Run function
_add.d = func(unpack(func_args))
end
if _add.d == nil or _add.d == nil_param then
-- have to do pagename defaults here to prevent weird behaviour with switch values
if v == 'name' then
_add.d = pagename
else
_add.d = edit
end
catdata.all_defined = false
else
--_add.d is not nil
catdata.one_defined = true
end
if self.switchfo then
-- Table of switches values and count of them
local _add_switch = {}
local switches = 0
-- Look for up to the maximum number
for i=1, self.versions do
local _addarg
-- see if this param is allowed to have switch
if v ~= 'image' and v ~= 'examine' and string.find(self.args[v..i] or '','%$%d') then
local refi = string.match(self.args[v..i],'%$(%d+)')
_addarg = _add_switch[tonumber(refi)] or nil_param
else
-- If the value of params is a function
if type(self.params[v]) == 'function' then
-- Perform that function with the parameter at that index
_addarg = self.params[v](self.args[v..i])
-- If it's a table, parse it into a function
elseif type(self.params[v]) == 'table' then
-- Find the functions name
local func = self.params[v].name
if type(func) == 'string' then
func = func_map[func]
end
-- catch all
if type(func) ~= 'function' then
func = has_content
end
-- Recreate table of args and flags
local func_args = {}
local flag = {}
-- If the flags are NOT a table, turn them into a table
-- Same size as the parameter table
-- Every flag will be the same
if type(self.params[v].flag) ~= 'table' then
-- Map flags, if unmapped, use default
local _flag = flagmap[self.params[v].flag] or 'd'
-- recreate table
for x=1,#self.params[v].params do
table.insert(flag,_flag)
end
-- If flags are already a table, recreate them in new table
elseif type(self.params[v].flag) == 'table' then
local _flag = self.params[v].flag
-- recreate table
for x=1,#self.params[v].params do
-- Map flags, if unmapped, use default
table.insert(flag,flagmap[_flag[x]] or 'd')
end
end
-- Recreate param table, parsing flags as instructions
for x, w in ipairs(self.params[v].params) do
local xarg
-- By default or defined as 'd'
-- looks for the cleaned value of the named parameter first
-- if it doesn't exist, look at the passed value next
-- if that doesn't exist, look at the default
-- if that doesn't exist, use blank
if flag[x] == 'd' then
if self.rargs[w] then
if self.rargs[w].switches then
xarg = self.rargs[w].switches[i]
else
xarg = self.args[w..i]
end
if xarg == nil or xarg == nil_param then
xarg = self.rargs[w].d
end
end
-- multiple catches in a row just to cover everything
if xarg == nil or xarg == nil_param then
xarg = self.args[w..i]
end
if xarg == nil or xarg == edit or xarg == nil_param then
xarg = self.args[w]
end
if xarg == nil or xarg == edit or xarg == nil_param then
xarg = ''
end
-- Look only at the passed value of the named parameter
-- if that doesn't exist, use unnumbered parameter
-- if that doesn't exist, use blank
elseif flag[x] == 'p' then
xarg = self.args[w..i] or self.args[w] or ''
-- Don't interpret value as a parameter name, and paste the as is
elseif flag[x] == 'r' then
xarg = w
end
-- Add parsed argument to table
table.insert(func_args,xarg)
end
-- Run function
_addarg = func(unpack(func_args))
end
end
-- If not defined, add the nil_param value
-- An actual nil would cause errors in placement
-- So it needs to be filled with an actual value
-- "nil_param" is understood as nil in other functions
-- Include table in case parameter isn't defined by template
if _addarg == nil or _addarg == nil_param then
table.insert(_add_switch, nil_param)
else
switches = switches + 1
table.insert(_add_switch, _addarg)
catdata.one_defined = true
end
end
-- If there are actually other values to switch to
-- Define a switches subtable, otherwise ignore it
if switches > 0 then
_add.switches = _add_switch
end
end
-- Quick fix for names (which defaults to pagename)
if v == 'name' then
if _add.d == pagename then
if _add.switches and _add.switches[1] ~= pagename and _add.switches[1] ~= nil_param then
_add.d = _add.switches[1]
end
end
end
-- Parameter cleaning finished, add to table of cleaned args
self.rargs[v] = _add
-- Category metadata
-- If every param except default is defined, all_defined = true
if catdata.all_defined == false then
if _add.d == edit then
if _add.switches then
catdata.all_defined = true
for _, v in ipairs(_add.switches) do
if v == nil_param then
catdata.all_defined = false
break
end
end
end
end
end
self.catdata[v] = catdata
end
-- mass dupe removal
-- this needs to be done at the end to keep dependent parameters working
-- also removes incompatible data types
for _, v in ipairs(self.paramnames) do
-- not removed from dupe enabled params parameters
if not self.dupeable[v] and self.rargs[v] and self.rargs[v].switches then
-- tells us whether or not we'll need to remove the switch data
-- switched to false if a switch values does not match the default
local rmvswitch = true
for q, z in ipairs(self.rargs[v].switches) do
-- remove types that don't turn into strings properly
if type(z) == 'table' or type(z) == 'function' then
self.rargs[v].switches[q] = nil_param
-- if it isn't nil or an edit button
-- change variable to keep the switch data
elseif z ~= nil_param and z ~= edit then
rmvswitch = false
end
end
-- remove switch data if everything was a dupe
if rmvswitch then
self.rargs[v].switches = nil
end
end
end
-- Title parentheses (has to be done here, sadly)
local _name
if self.rargs.name then
_name = self.rargs.name.d
-- replace html entities to their actual character
_name = mw.text.decode(_name)
-- if the page name matches the item name, don't add parentheses
if _name == mw.title.getCurrentTitle().fullText then
self.rtable:addClass('no-parenthesis-style')
end
end
end
--[[
Function to link internal use parameters with JS class attribution
-- self:linkParams{ { arg1, arg2 }, ... { arg1a, arg2a } }
-- arg1: parameter name being linked
-- arg2: parameter name that holds the classes
-- THIS FUNCTION SHOULD BE RUN AFTER :cleanParams()
-- THIS FUNCTION SHOULD BE RUN BEFORE :finish()
-- The second argument's data should always contain a value (a CSS class name) at every index
-- This function is cancelled for non switch boxes
--]]
function Infobox:linkParams(...)
if not self.switchfo then
return
end
for _, v in ipairs(...) do
self.switchfoattr[v[1]] = self.rargs[v[2]]
end
end
--[==========================================[
-- Functions for accessing parameters easily
--]==========================================]
--[[
Access the param
-- arg : param name
-- retp : return type
d | #default : self.rargs[arg].d -- Default value
f | full : self.rargs[arg] -- Entire table
s | switches : self.rargs[arg].switches -- Entire switch table
s# : self.rargs[arg].switches[#] -- Single switch value at index #
r : switches[1] or d
--]]
function Infobox:param(arg, retp)
-- All parameters
if arg == 'all' then
return self.rargs
end
-- case-insensitive flagging
retp = tostring(retp):lower()
local fmap = {
d = 'd', default = 'd', s0 = 'd', -- let s-naught count as default (since that's what it is)
f = 'f', full = 'f',
s = 's', switch = 's', switches = 's',
r = 'r'
}
local ret_func
-- quickly see if the parameter is a value greater than 0
if retp:match('s[1-9]') then
ret_func = 's2'
else
-- Otherwise map it to the correct flag, or the default
ret_func = fmap[retp] or fmap.d
end
-- Fetch parameter
local param = self.rargs[arg]
-- Return nil if no table found
if not param then return nil end
-- Return default
if ret_func == 'd' then
return param.d
end
-- Return full table
if ret_func == 'f' then
return param
end
-- Return switch table
if ret_func == 's' then
return param.switches
end
-- Return the first switch, otherwise the default
if ret_func == 'r' then
if not param.switches then
return param.d
elseif param.switches[1] == nil_param then
return param.d
else
return param.switches[1]
end
end
-- If s2, reread the param
if ret_func == 's2' then
-- no switches
if not param.switches then
return nil
end
-- Parse index by removing the s
local index = retp:match('s(%d+)')
-- nil_param
if param.switches[index] == nil_param then
return nil
else
return param.switches[index]
end
end
end
--[[
Checks if a parameter is defined and not blank
-- arg : parameter to look at
-- index : index of switches to look at (defaults to default param)
-- defining 'all' will look at every possible value for the parameter
--]]
function Infobox:paramDefined(arg,index)
-- Can use cleaned params for switches
-- but need the passed to identify blanks in the template
local param = self.rargs[arg]
local _arg = self.args[arg]
if string.find(_arg or '','%?action=edit') then
_arg = ''
end
index = index or 0
local ret
-- create a long strong of every value to test for things if 'all'
if string.lower(index) == 'all' then
return self.catdata[arg] and (self.catdata[arg].one_defined or self.catdata[arg].all_defined)
-- index to number otherwise
else
index = tonumber(index) or 0
if index == 0 then
if param.switches then
if Infobox.isDefined(param.switches[1]) then
ret = param.switches[1]
else
ret = _arg
end
else
ret = _arg
end
else
if not param.switches then
return nil
end
if param.switches[index] == nil_param then
return nil
end
ret = param.switches[index]
end
end
return tostring(ret or ''):find('%S')
end
--[[
Function to perform a search on all parameters of a defined name
-- param: param name
-- val: a value or function
-- functions passed must return either true or false
-- with true being counted as a match
--]]
function Infobox:paramGrep(param,val)
local arg = self.rargs[param]
-- if no parameters, return nil
if not arg then
return nil
end
local ret
local valtype = type(val)
-- start with the default value
-- if it's a function, run it
if valtype == 'function' then
ret = val(arg.d)
-- true means it matched
if ret == true then
return ret
end
-- switches up here for functions
if arg.switches then
for _, v in ipairs(arg.switches) do
ret = val(v)
if ret == true then
return true
end
end
end
-- if it's just a value, compare the two
-- only run if types match to avoid errors
-- compare switches later
elseif valtype == type(arg.d) then
-- if a string, make case insensitive
if valtype == 'string' then
if string.lower(val) == string.lower(arg.d) then
return true
end
-- everything else just test directly
elseif val == arg.d then
return true
end
end
-- switch cases
-- more organised putting them here
if arg.switches then
for _, v in ipairs(arg.switches) do
if valtype == type(v) then
if valtype == 'string' then
if val:lower() == v:lower() then
return true
end
elseif val == v then
return true
end
end
end
end
-- return false in every other case
return false
end
------
function Infobox.paramRead(arg,val)
-- if no parameters, return nil
if not arg then
return nil
end
local ret
local valtype = type(val)
-- start with the default value
-- if it's a function, run it
if valtype == 'function' then
ret = val(arg.d)
-- true means it matched
if ret == true then
return ret
end
-- switches up here for functions
if arg.switches then
for _, v in ipairs(arg.switches) do
ret = val(v)
if ret == true then
return true
end
end
end
-- if it's just a value, compare the two
-- only run if types match to avoid errors
-- compare switches later
elseif valtype == type(arg.d) then
-- if a string, make case insensitive
if valtype == 'string' then
if string.lower(val) == string.lower(arg.d) then
return true
end
-- everything else just test directly
elseif val == arg.d then
return true
end
end
-- switch cases
-- more organised putting them here
if arg.switches then
for _, v in ipairs(arg.switches) do
if valtype == type(v) then
if valtype == 'string' then
if val:lower() == v:lower() then
return true
end
elseif val == v then
return true
end
end
end
end
-- return false in every other case
return false
end
----
-- Return collected category data
function Infobox:categoryData()
return self.catdata
end
-- Infobox:addDropLevelVars("thieving", "skilllvl1")
function Infobox:addDropLevelVars(key, paramName)
local levelParams = self:param(paramName, 'f')
local dropParams = self:param('dropversion', 'f')
if levelParams == nil then
return
end
if dropParams == nil or not self:paramDefined("dropversion", "all") then
dropParams = {d = 'DEFAULT'}
end
if dropParams.switches == nil then
dropParams.switches = {}
end
local levels = levelParams.switches or {levelParams.d}
local dropVersions = {}
for i=1,#levels do
local dropVersionFromInfobox = dropParams.switches[i] or dropParams.d
if dropVersionFromInfobox == nil_param then
dropVersionFromInfobox = 'DEFAULT'
else
dropVersionFromInfobox = dropVersionFromInfobox .. ",DEFAULT"
end
for dropVersion in string.gmatch(dropVersionFromInfobox, ' *([^,]+) *') do
if dropVersions[dropVersion] == nil then
dropVersions[dropVersion] = {}
end
dropVersions[dropVersion][levels[i]] = true
end
end
-- This part is to append levels from previous Infobox invocations
for dropVersion, dropLevels in pairs(dropVersions) do
-- 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 v in string.gmatch(previousVar, ' *([^,]+) *') do
dropVersions[dropVersion][v] = true
end
end
local ordered = {}
for k, v in pairs(dropVersions[dropVersion]) do
local n = tonumber(k)
if n ~= nil then
table.insert(ordered, n)
end
end
table.sort(ordered)
var.vardefine(var_name, table.concat(ordered, ','))
end
end
-- Override tostring
function Infobox.tostring(box)
-- If not finished, finish
if not box.__finished then
box:finish()
end
-- Make entire html wrapper a string and return it
local btns = box.switch_buttons_tag
if box.custom_buttons then
btns = ''
end
if box.args.__dump__ then
return '<' .. 'pre>'..mw.dumpObject(box) .. '</' .. 'pre>[[Category:Dumping infoboxes]]'
end
return tostring(btns) .. tostring(box.rtable) .. table.concat(box.appendStrs, '') .. tostring(box.switch_tag) .. tostring(box.smw_error_tag)
end
return Infobox
-- </nowiki>