Module:InfoboxV2
Documentation for this module may be created at Module:InfoboxV2/doc
local Infobox = {} local metatable = {} local methodtable = {} metatable.__index = methodtable metatable.__tostring = function( t ) return tostring( t:getBoxText() ) end -- Uses capiunto as the base local capiunto = require 'capiunto' local libraryUtil = require( 'libraryUtil' ) --- Returns true if input is a string or number --- @param input string|number|table --- @return boolean local function verifyStringNum( input ) return type( input ) == 'string' or type( input ) == 'number' end --- This fills the box with content local function addBoxContent( t ) if t.contentAdded then return end for _, row in ipairs( t.rows ) do local label = tostring( row.label ) if row.type == 'row' then if (type( label ) == 'string' or type( label ) == 'number') and ( type( row.data ) == 'string' or type( row.data ) == 'number' ) then t.capiunto:addRow( label, row.data, row.class, row.rowClass ) end elseif row.type == 'header' or row.type == 'image' then if row.type == 'image' and row.rowClass ~= 'row-image' then row.rowClass = row.rowClass .. ' row-image' end -- Only add the header if it has content if mw.ustring.find( row.rowClass, 'row-header', 1, true ) ~= nil and t.headerContentCounts[ label ] ~= nil and t.headerContentCounts[ label ] == 0 then -- skip add else t.capiunto:addRow( nil, label, row.class, row.rowClass ) end elseif row.type == 'subheader' then t.capiunto:addSubHeader( label, row.class, row.style ) end end t.contentAdded = true end --- Iterate table in key order local function spairs( t, orderFn ) -- collect the keys local keys = {} for k, v in pairs( t ) do if v ~= nil then keys[ #keys + 1 ] = tostring( k ) end end -- if order function given, sort by it by passing the table and keys a, b, -- otherwise just sort the keys if orderFn then table.sort( keys, function( a, b ) return orderFn( t, a, b ) end ) else table.sort( keys ) end -- return the iterator function local i = 0 return function() i = i + 1 if keys[ i ] then return keys[ i ], t[ keys[ i ] ] end end end --- Parse all available capiunto options from a table --- Returns only non nil values --- --- @param args table --- @return table local function parseArgs( args ) local options = { [ 'isChild' ] = args[ 'isChild' ] or nil, [ 'isSubbox' ] = args[ 'isSubbox' ] or nil, [ 'title' ] = args[ 'title' ] or nil, [ 'titleClass' ] = args[ 'titleClass' ] or nil, [ 'titleStyle' ] = args[ 'titleStyle' ] or nil, [ 'top' ] = args[ 'top' ] or nil, [ 'topClass' ] = args[ 'topClass' ] or nil, [ 'topStyle' ] = args[ 'topStyle' ] or nil, [ 'captionStyle' ] = args[ 'captionStyle' ] or nil, [ 'imageStyle' ] = args[ 'imageStyle' ] or nil, [ 'imageClass' ] = args[ 'imageClass' ] or nil, [ 'bodyClass' ] = args[ 'bodyClass' ] or nil, [ 'bodyStyle' ] = args[ 'bodyStyle' ] or nil, [ 'headerStyle' ] = args[ 'headerStyle' ] or nil, [ 'labelStyle' ] = args[ 'labelStyle' ] or nil, [ 'dataStyle' ] = args[ 'dataStyle' ] or nil, } local i = 0 local keys, values = {}, {} for k, v in pairs( options ) do i = i + 1 keys[ i ] = k values[ i ] = v end while i > 0 do if keys[ i ] == nil then table.remove( keys, i ) table.remove( values, i ) break end i = i - 1 end local finalOptions = {} for i = 1, #keys do finalOptions[ keys[ i ] ] = values[ i ] end local finalArgs = {} for k, v in pairs( args ) do if finalOptions[ k ] == nil and v ~= nil then finalArgs[ k ] = v end end return finalOptions, finalArgs end --- Replaces a row if labels match and returns the replaced index --- Otherwise returns the next free index --- --- @return number New index local function replaceInRows( t, targetLabel ) for idx, row in ipairs( t.rows ) do if row.label == targetLabel then table.remove( t.rows, idx ) return idx end end return #t.rows + 1 end --- Adds one of three special rows to the infobox --- As defined by Capiunto --- Available rowTypes are: title (caption), top (first row), bottom (last row) --- --- @param t table The infobox --- @param rowType string title, top or bottom --- @param class string Row class --- @param style string Css style --- @return table local function addSpecialRow( t, rowType, text, class, style ) if t.capiunto == nil then return end if verifyStringNum( text ) then t.capiunto.args[ rowType ] = text end if type( class ) == 'string' then t.capiunto.args[ rowType .. 'Class' ] = class end if type( style ) == 'string' then t.capiunto.args[ rowType .. 'Style' ] = style end return t end --- Generates a link to Special:Upload with pre-defined parameters --- --- @param fileName string|nil The filename including the extension --- @param title string The title of the infobox local function generateUploadLink( fileName, category ) local title = mw.title.new( 'Special:Upload' ) if fileName == nil then fileName = mw.title.getCurrentTitle().text .. '.jpg' end if category == nil then category = mw.title.getCurrentTitle().text end local description = [==[ =={{int:filedesc}}== {{Information |description={{en|1=%s}} |date=%s |source=<!-- Please insert link to picture --> |author=RSI |permission= |other versions= }} =={{int:license-header}}== {{license-rsi}} [[Category:%s]] ]==] description = mw.ustring.format( description, category, mw.getContentLanguage():formatDate('Y-m-d H:i:s'), category ) local parameters = { [ 'wpDestFile' ] = fileName, [ 'wpLicense' ] = 'license-rsi', [ 'wpUploadDescription' ] = description } return title:fullUrl( parameters, 'https' ) end --- Generates a link to Special:Uploadwizard with pre-defined parameters --- --- @param fileName string|nil The filename excluding the extension --- @param title string The title of the infobox local function generateUploadwizardLink( fileName, category ) local title = mw.title.new( 'Special:Uploadwizard' ) if fileName == nil then fileName = mw.title.getCurrentTitle().text end if category == nil then category = mw.title.getCurrentTitle().text end local parameters = { [ 'categories' ] = category, [ 'title' ] = fileName, [ 'description' ] = fileName, -- Missing: Date -- Missing pre license select } return title:fullUrl( parameters, 'https' ) end --- Base method to add a row --- @see https://www.mediawiki.org/wiki/Extension:Capiunto/Infobox --- @param t table The instance --- @param rowLabel string Row label --- @param rowData string Row content (can be anything) --- @param dataClass string CSS class added to data --- @param rowCssClass string CSS class added to row --- @param rowStyle string CSS style only used if type is 'subheader'! --- @param type string Either 'row', 'header', 'image' or 'subheader' function methodtable.addRow( t, rowLabel, rowData, dataClass, rowCssClass, rowStyle, type ) t.checkSelf( t, 'addRow' ) type = type or 'row' if t.removeEmpty == true then if type == 'row' and ( rowData == nil or rowData == t.emptyString ) then return t end end if type == 'header' then t.currentHeader = rowLabel if t.headerContentCounts[ rowLabel ] == nil then local count = 0 -- A "single" header is a header without content -- This is used to circumvent removing the header as it as no content if dataClass == 'single' then count = 1 end t.headerContentCounts[ rowLabel ] = count end end -- increment the header count if t.currentHeader ~= nil and type == 'row' and t.headerContentCounts[ t.currentHeader ] ~= nil then t.headerContentCounts[ t.currentHeader ] = t.headerContentCounts[ t.currentHeader ] + 1 end local pos = -1 if t.allowReplace == true and #t.rows > 0 then pos = replaceInRows( t, rowLabel ) else t.rowCount = t.rowCount + 1 pos = t.rowCount end if rowCssClass == nil then rowCssClass = 'row-' .. type if rowCssClass == 'row-row' then rowCssClass = 'row' end end if not mw.ustring.match( rowCssClass, 'row' ) then local toAdd = type if toAdd ~= 'row' then toAdd = 'row-' .. type end rowCssClass = rowCssClass .. ' ' .. toAdd end table.insert( t.rows, pos, { type = type, label = rowLabel, data = rowData or nil, class = dataClass or nil, rowClass = rowCssClass, style = rowStyle or nil }) return t end --- Adds a header to the infobox function methodtable.addHeader( t, text, class, rowClass ) t.checkSelf( t, 'addHeader' ) rowClass = ( rowClass or '' ) .. ' row-header' return t:addRow( text, nil, class, rowClass, nil, 'header' ) end --- Adds a title row to the infobox function methodtable.addTitle( t, text, class, rowClass ) t.checkSelf( t, 'addTitle' ) rowClass = ( rowClass or '' ) .. ' row-title' return t:addRow( text, nil, class, rowClass, nil, 'header' ) end --- Adds a subheader to the infobox function methodtable.addSubHeader( t, text, class, style ) t.checkSelf( t, 'addSubHeader' ) return t:addRow( text, nil, class, 'row-subheader', style, 'subheader' ) end --- Adds a caption to the infobox function methodtable.addCaption( t, text, class, style ) t.checkSelf( t, 'addCaption' ) return addSpecialRow( t, 'title', text, class, style ) end --- Adds a caption to the infobox function methodtable.addTop( t, text, class, style ) t.checkSelf( t, 'addTop' ) return addSpecialRow( t, 'top', text, class, style ) end --- Adds a bottom to the infobox function methodtable.addBottom( t, text, class, style ) t.checkSelf( t, 'addBottom' ) return addSpecialRow( t, 'bottom', text, class, style ) end --- Adds an image to the infobox --- @param file string Wiki page filename --- @param options table Image options --- @param checkExistence boolean True to check if the file exists function methodtable.addImage( t, file, options, checkExistence ) t.checkSelf( t, 'addImage' ) local isPlaceholder = false if type( file ) ~= 'string' then if t.displayPlaceholder == false then return end file = t.placeholderImage isPlaceholder = true end local exists = true local title = mw.title.new( mw.uri.decode( file, 'WIKI' ), 6 ) if title == nil then return end if type( options ) == 'string' then options = { [ 'rowClass' ] = options } else options = options or {} end if checkExistence ~= nil and checkExistence == true then exists = title.exists end local class = options[ 'rowClass' ] or nil local function buildOptions( imageOptions ) local out = {} for k, v in pairs( imageOptions ) do if k ~= 'rowClass' and type( k ) == 'string' and type( v ) == 'string' then table.insert( out, k .. '=' .. v ) elseif type( k ) == 'number' then table.insert( out, v ) end end return table.concat( out, '|' ) end if isPlaceholder == true then options[ 'link' ] = generateUploadLink() if class == nil then class = 'placeholder' else class = class .. ' placeholder' end end local imageOptions = buildOptions( options ) if imageOptions ~= '' then imageOptions = '|' .. imageOptions if isPlaceholder == true then imageOptions = imageOptions .. '|Klicke um Bild hochzuladen' end end local header = '[[' .. title.prefixedText .. imageOptions .. ']]' if exists == false then header = 'Datei fehlt' end return t:addRow( header, nil, class, 'row-image', nil, 'image' ) end --- Allows to add arbitrary rows from template args --- @param args table Template arguments --- @param prefix string|nil An optional prefix that each argument must have to be added as a row --- @param allowedKeys table|nil An optional table of keys that are allowed as rows, are passed to ustring.match --- --- Example param = !row -> Only Template arguments in the form of |!row...=Content are added as Rows --- The prefix gets replaced from each key: |!rowLabel=Content => Label=Content --- --- The row type can be set by adding 'subheader' or 'header' to the key --- Example: |header1=Content => Header row with content 'content' added --- Multiple headers must be suffixed by numbers --- --- !!! NOTE !!! --- Lua tables do not preserve order, to mitigate this, this module SORTS the given arguments ALPHABETICALLY --- You can prefix arguments with numbers to preserve the order --- Example Template: --- {{Infobox --- |1-header=Header for block one --- |1.1-RowLabel=This is the content for row 1.1 --- |1.2-RowLabel=This again is the content --- |2-subheader=Subheader --- |2.1-Label=Content --- |2.2... --- }} --- --- Example Module Call: --- infobox.addRowsFromArgs({ --- [ '1-header' ] = 'Header for block one', --- [ '1.1-RowLabel' ] = 'This is...', --- }) function methodtable.addRowsFromArgs( t, args, prefix, allowedKeys ) t.checkSelf( t, 'addRowsFromArgs' ) if type( args ) ~= 'table' then return end _, args = parseArgs( args ) local function canAdd( key ) if key == 'image' then -- Image is added separately return false end if prefix == nil then if type( allowedKeys ) == 'table' then for _, allowed in pairs( allowedKeys ) do if mw.ustring.match( key, allowed ) then return true end end return false end return true elseif type( prefix ) == 'string' then return mw.ustring.match( key, prefix ) end return true end for k, v in spairs( args ) do if type( k ) == 'string' and type( v ) == 'string' then if canAdd( k ) then if prefix~= nil and type( prefix ) == 'string' then k = mw.ustring.gsub( k, prefix, '' ) end -- Remove Digits - and . at start of label k = mw.ustring.gsub( k, '^[%d%-%.]+', '' ) -- Removes digits at the end, should enable MW Infobox behaviour k = mw.ustring.gsub( k, '[%d]+$', '' ) local splitted = mw.text.split( v, '<>', true ) local label = splitted[1] local class = splitted[2] or '' local rowClass = splitted[3] or '' if mw.ustring.match( k, 'subheader' ) then t:addSubHeader( v ) elseif mw.ustring.match( k, 'header' ) then t:addHeader( label, class, rowClass ) elseif mw.ustring.match( k, 'title' ) then t:addTitle( label, class, rowClass ) elseif mw.ustring.match( k, 'caption' ) then t:addCaption( label, class, rowClass ) elseif mw.ustring.match( k, 'top' ) then t:addTop( label, class, rowClass ) elseif mw.ustring.match( k, 'bottom' ) then t:addBottom( label, class, rowClass ) else t:addRow( k, label, class, rowClass ) end end end end return t end --- Flag to allow or disable row replacing --- @param flag boolean function methodtable.setAllowReplace( t, flag ) t.checkSelf( t, 'setAllowReplace' ) t.allowReplace = flag end --- Returns the raw capiunto box --- @return table function methodtable.getBox( t ) t.checkSelf( t, 'getBox' ) addBoxContent( t ) return t.capiunto end --- Returns the table string --- @return string function methodtable.getBoxText( t ) t.checkSelf( t, 'getBoxText' ) return tostring( t:getBox() ) end --- Init the infobox --- @param options table|nil Option table passed to capiunto.create function Infobox.create( options ) local instance = { -- Table containing Header = count of rows headerContentCounts = {}, -- The currently active header currentHeader = nil, -- The row tables rows = {}, -- Total number of rows in the box rowCount = 0, -- Capiunto table capiunto = {}, -- Flag to stop adding rows to the box contentAdded = false, -- Flag to enable replacing already added rows allowReplace = false, -- Flag to discard empty rows removeEmpty = false, -- Optional string which is valued as empty emptyString = nil, -- Display a placeholder image if addImage does not find an image displayPlaceholder = true, -- Placeholder Image placeholderImage = 'Platzhalter.webp', } if options.allowReplace ~= nil then instance.allowReplace = options.allowReplace options.allowReplace = nil end if options.removeEmpty ~= nil then instance.removeEmpty = true options.removeEmpty = nil end if options.emptyString ~= nil then instance.emptyString = options.emptyString options.emptyString = nil end if options.displayPlaceholder ~= nil then instance.displayPlaceholder = options.displayPlaceholder options.displayPlaceholder = nil end if options.placeholderImage ~= nil then instance.placeholderImage = options.placeholderImage options.placeholderImage = nil end setmetatable( instance, metatable ) instance.capiunto = capiunto.create( parseArgs( options or {} ) ) instance.checkSelf = libraryUtil.makeCheckSelfFunction( 'Infobox', 'instance', instance, 'Method Call' ) return instance end --- Create a infobox from args --- @param frame table --- @return string function Infobox.fromArgs( frame ) local arguments = require( 'Module:Arguments' ).getArgs( frame ) local options, args = parseArgs( arguments ) if options.bodyClass == nil then options.bodyClass = 'floatright' end local box = Infobox.create( options ) box:addImage( arguments.image,{ 'frameless', '360px' } ) if arguments.image ~= nil then arguments.image = nil end box:addRowsFromArgs( args ) return tostring( box ) end return Infobox