Module:InfoboxV2
Jump to navigation
Jump to search
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',
'300px'
} )
if arguments.image ~= nil then
arguments.image = nil
end
box:addRowsFromArgs( args )
return tostring( box )
end
return Infobox