Modul:Smartbox
Utseende
Moduldokumentasjon
Parser
[rediger kilde]Hver enkelt verdi som brukes må passe med en parser for den verdien. Det blir først valgt en tolk (parser) på bakgrunn av typen til feltet, denne blir satt i konfigurasjonen, typisk fra en oppføring malens TemplateData, som så vil angi en systemmelding og dennes argumenter. Dette er skilt fra hvordan radene legges ut, dermed kan utlegget av selve verdien endres etter dennes innhold.
- boolean
- Noen få tallverdier for å angi boolske verdier; «0» for false, «1» for true, «» for ukjent. Kan feile.
- number
- En streng av tegn for å angi et tall, med tillegg av fortegn og separatorer. Prefiks og suffiks vil bli skilt ut, og innledende og avsluttende mellomrom vil bli strippet. Prefiks og suffiks vil bli forsøkt tolket, og kan da bli lenket. Kan feile.
- string
- En streng av tegn, innledende og avsluttende mellomrom vil bli strippet. Strengen vil bli escaped. Vil aldri feile.
- unknown
- En streng av tegn, innledende og avsluttende mellomrom vil bli strippet. Strengen vil bli escaped. Vil aldri feile.
- wiki-file-name
- En streng av tegn for å representere et filnavn, innledende og avsluttende mellomrom vil bli strippet. Må kunne tolkes som et filnavn. (Filer kommer fra Commons.)
- wiki-page-name
- En streng av tegn for å representere et sidenavn, innledende og avsluttende mellomrom vil bli strippet. Må kunne tolkes som et sidenavn.
- wiki-user-name
- En streng av tegn for å representere et brukernavn, innledende og avsluttende mellomrom vil bli strippet. Må kunne tolkes som navn på en rotside i brukerrommet
- wiki-template-name
- En streng av tegn for å representere et malnavn, innledende og avsluttende mellomrom vil bli strippet. Må kunne tolkes som et lokalt sidenavn fra malrommet.
- date
- En streng av tegn for å angi en dato. Kan feile.
- url
- En streng av tegn for å angi en url. Kan feile.
- content
- Full wikitekst. Vil aldri feile.
- line
- Full wikitekst. Vil aldri feile.
- unbalanced-wikitext
- Bør ikke brukes, og er ikke implementert.
Det kan bli nødvendig å endre tolkene hvis (eventuelt når) verdiene blir mer formalisert.
-- module for generating smart infoboxes
-- © John Erling Blad, Creative Commons by Attribution 3.0
-- don't pollute with globals
require('strict')
--- Library to parse paths and and get values from entities
local wikibase = nil
--- @fixme
local classes = {}
--- Extensions to override the default behavior
local extensions = {}
--- The local language
local lang = mw.getContentLanguage()
--- Warning messages
local warnings = mw.loadData( 'Module:Smartbox/warnings' )
--- Formatter messages
local messages = mw.loadData( 'Module:Smartbox/messages' )
--- Preformatted units
local units = mw.loadData( 'Module:Units' )
--- Parsers for arguments
local parsers = require( 'Module:Smartbox/parsers' )
--- Initializer for parsers
-- This list must contain all types
local types = { 'unknown', 'number', 'string', 'boolean', 'date', 'url',
'wiki-page-name', 'wiki-file-name', 'wiki-user-name', 'wiki-template-name',
'content', 'line', 'unbalanced-wikitext' }
for k,_ in pairs( parsers) do
if not types[k] then
types[k] = {}
end
end
--for k,v in pairs( parserTypes) do
-- parsers[v] = {}
--end
local function ucFirst( str )
return mw.ustring.upper( mw.ustring.sub( str, 1, 1 ) ) .. mw.ustring.sub( str, 2 )
end
--- Escape strings to make them safe to use as identifiers and class names
-- Because strings are non-mutable this will create a copy, and not change the source
-- @param str to be escaped
-- @result string escaped
local function escape( str )
if str then
str = string.gsub( str, "[%s]", "_" )
str = string.gsub( str,
"([^%w%-%_])",
function( char )
return string.format ( "$%02X", string.byte( char ) )
end
)
end
return str
end
--- Generate a specific warning message
-- This will build a proper warning message for the column (can also be 'row')
-- name, and type. Can be chained.
local function failed( col, name, type )
local escapedCol = escape( col )
local escapedType = escape( type )
local html = mw.html.create( 'span' ):addClass( 'sb-warning' )
local msg = mw.message.newFallbackSequence( 'smartbox-' .. escapedCol .. '-unknown-' .. type, 'smartbox-' .. escapedCol .. '-unknown' )
if warnings['smartbox-' .. escapedCol .. '-unknown'] and not msg:exists() then
msg = mw.message.newRawMessage( warnings['smartbox-' .. escapedCol .. '-unknown'] )
end
html:wikitext( msg:params( name, type ):plain() )
return html
end
--- Merge two tables
-- This merges to tables, typically so the new table keeps the values from the
-- last table.
local function merge( t1, t2 )
for k,v in pairs( t2 ) do
if (type(v) == "table") and (type(t1[k] or false) == "table") then
merge( t1[k], t2[k] )
elseif tonumber( k ) then
table.insert( t1, v )
else
t1[k] = v
end
end
return t1
end
--- An internal extension to change the behavior when rendering an image
extensions['image'] = function( args, params, extract )
local str = extract.argument.value
local title, value
if str then
title = mw.text.nowiki( mw.text.unstrip( str ) )
value = ('[[File:' .. title .. '|frameless|upright=1|alt=' .. title .. ']]')
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 4 )
if value then
td:wikitext( value )
else
td:node( failed( 'title', extract.name, 'image' ) )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( td )
return tr
end
--- The core parser before dispatching
local function parse( str, params, lang )
if parsers[params.type] then
for _,v in ipairs( parsers[params.type] ) do
if str:match( v[1] ) then
local keys = {}
local key, parts = v[2]( str, params )
if params.classes then
for _,cls in ipairs(params.classes) do
keys[1+#keys] = 'smartbox-' .. cls .. '-' .. key
end
else
keys[1+#keys] = 'smartbox-' .. key
end
local msg = mw.message.newFallbackSequence( unpack( keys ) )
if lang then
msg:inLanguage( lang )
elseif not msg:exists() then
msg = mw.message.newRawMessage( messages['smartbox-' .. key] or ('<smartbox-' .. key .. '>'))
end
return msg:params( unpack( parts ) ):plain()
end
end
end
return str
end
local renders = {}
--- The renderer for unknown type
renders['unknown'] = function( args, params, extract )
local value = parse( extract.argument.value, params ) or nil
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'unknown' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'unknown' ) )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
:node( td )
return tr
end
--- The renderer for number type
renders['number'] = function( args, params, extract )
local value = parse( extract.argument.value, params ) or nil
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'number' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'number' ) )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
:node( td )
return tr
end
--- The renderer for string type
renders['string'] = function( args, params, extract )
local value = parse( extract.argument.value, params ) or nil
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'string' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'string') )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
:node( td )
return tr
end
--- The renderer for unknown type
renders['boolean'] = function( args, params, extract )
local value = mw.text.nowiki( extract.argument.value )
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'boolean' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'boolean') )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
:node( td )
return tr
end
--- The renderer for date type
renders['date'] = function( args, params, extract )
local str = extract.argument.value
local value = str and lang:formatDate( 'j. M Y', str, true ) or nil
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'date' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'date') )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
:node( td )
return tr
end
--- The renderer for wiki-page-name type
renders['wiki-page-name'] = function( args, params, extract )
local str = extract.argument.value
local title, text, value
if str then
title = mw.text.nowiki( mw.text.unstrip( str ) )
text = title:gsub( '%s*%(.-%)%s*$', '' )
value = title == text and ('[[' .. title .. ']]') or ('[[' .. title .. '|' .. text .. ']]')
end
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'wiki-page-name' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'wiki-page-name') )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
:node( td )
return tr
end
--- The renderer for wiki-file-name type
renders['wiki-file-name'] = function( args, params, extract )
local str = extract.argument.value
local title, value
if str then
title = mw.text.nowiki( mw.text.unstrip( str ) )
value = ('[[File:' .. title .. '|frameless|upright|alt=' .. title .. ']]')
end
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'wiki-file-name' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'wiki-file-name') )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
:node( td )
return tr
end
--- The renderer for wiki-user-name type
renders['wiki-user-name'] = function( args, params, extract )
local value = "Not implemented"
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'wiki-user-name' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'wiki-user-name') )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
return tr
end
--- The renderer for content type
renders['content'] = function( args, params, extract )
local value = mw.text.nowiki( extract.argument.value )
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'content' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'content') )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
:node( td )
return tr
end
--- The renderer for unbalanced-wikitext type
renders['unbalanced-wikitext'] = function( args, params, extract )
local value = "Should not be used"
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
td:node( failed( 'title', extract.name, 'unbalanced-wikitext') )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'unbalanced-wikitext') )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
return tr
end
--- The renderer for line type
renders['line'] = function( args, params, extract )
local value = mw.text.nowiki( extract.argument.value )
local th = mw.html.create( 'th' )
:attr( 'colspan', 2 )
if params.label then
th:wikitext( params.label )
else
th:node( failed( 'title', extract.name, 'line' ) )
end
local td = mw.html.create( 'td' )
:attr( 'colspan', 2 )
if value then
td:wikitext( value )
else
td:node( failed( 'value', extract.name, 'line') )
end
local tr = mw.html.create( 'tr' )
:addClass( escape( extract.name ) )
:node( th )
:node( td )
return tr
end
--- Report a message as a full-width row
-- @param msg to be wrapped in html code
-- @return html-wrapped string
local function report( msg )
local th = mw.html.create( 'th' )
:attr( 'colspan', 4 )
:wikitext( msg )
local tr = mw.html.create( 'tr' )
:node( th )
return tr
end
local function usePreferredName( key, arr, params )
return arr[key] and { found = arr[key] } or nil
end
local function useAlternateName( key, arr, params )
local aliases = params.aliases
if aliases then
for _,alias in ipairs( aliases ) do
if arr[alias] then
return { name = alias, found = arr[alias] }
end
end
end
return nil
end
local function useWikibaseLookup( params )
local stuff = wikibase.run( params.path )
return stuff and { found = stuff } or nil
end
local function useFallbackType( arr, params )
local type = params.type or 'unknown'
return arr[type] and { found = renders[type] } or nil
end
local function render( data, args )
local params = data.params
if not params then
return "''TemplateData: Missing params section''"
end
local rows = {}
local unhandled = {}
for k,v in pairs( args ) do
if type(k) == 'string' then
unhandled[k] = true
end
end
local infobox = data.sets and data.maps.infobox or {}
local exclude = {}
if infobox.exclude then
for i,v in ipairs( infobox.exclude ) do
unhandled[v] = nil
exclude[v] = true
end
end
local render, argument
local function helper( key, params )
render = usePreferredName( key, extensions, params ) or useAlternateName( key, extensions, params )
argument = usePreferredName( key, args, params ) or useAlternateName( key, args, params )
if not argument and params.path then
argument = useWikibaseLookup( params )
end
if argument and not render then
render = useFallbackType( renders, params )
end
if argument and render and not exclude[argument.name or key] then
unhandled[argument.name or key] = nil
argument['value'] = argument['found']
argument['found'] = nil
render['func'] = render['found']
render['found'] = nil
local extract = {}
extract['name'] = key
extract['argument'] = argument
extract['render'] = render
extract['classes'] = classes
return render.func( args, params, extract )
end
return nil
end
if data.paramOrder then
local paramOrder = data.paramOrder
for i,v in ipairs( paramOrder ) do
if params[v] then
local node = helper( v, params[v] )
if node then
rows[1+#rows] = node
end
end
end
else
for k,v in pairs( params ) do
if v then
local node = helper( k, v )
if node then
rows[1+#rows] = node
end
end
end
end
local html = mw.html.create( 'table' )
:addClass('infobox')
for _,v in ipairs( classes ) do
html:addClass( escape( v ) )
end
for _,v in ipairs( rows ) do
html:node( v )
end
for k,v in pairs ( unhandled ) do
html:node( report( tostring( failed( 'row', k, "undefined" ) ) ) )
end
return html
end
local function compile( namespace, text )
local title = mw.title.new( text, namespace )
if not title then
return nil
end
local content = title:getContent()
if not content then
return nil
end
local json = content:match('<templatedata[^>]*>(.-)</templatedata>')
if not json then
return nil
end
local data = mw.text.jsonDecode( json )
if not data then
return nil
end
return data
end
-- @var exported table
local Smartbox = {}
--- Build the box
-- This is the only exposed library function unless the library
-- are loaded inside module or module discussion namespaces
function Smartbox.build( frame )
local data, extra
data = {}
for _,v in ipairs( frame.args ) do
local str = mw.text.trim( v )
if str == '' then
-- do nothing
elseif v:match( '^%s*%{' ) then
-- inline json, mostly for testing
extra = mw.text.jsonDecode( v )
else
if str:match( '^[^:/%s]+$' ) then
-- seems like class markers, register them
classes[1+#classes] = str
elseif str:match( '^/' ) then
-- assumed to contain extension code
local lib = require( 'Module:Smartbox' .. str )
for k,v in pairs( lib ) do
if not k:match( '^_' ) then
extensions[k] = v
end
end
elseif str:match( '^[mM]al:' ) or str:match( '^[tT]emplate:' ) then
-- assumed to contain shared template data, compile and keep unless already done
--if data then
-- return "''Smartbox: TemplateData can only be registered once''"
--end
local namespace = str:match( '^(%S-)%s*:' )
local text = str:match( ':%s*(.-)%s*$' )
if namespace and text then
local tmpData = compile( namespace, text )
if tmpData then
extra = tmpData
end
end
else
--report this?
end
end
if extra then
merge( data, extra )
end
end
if not data then
return "''Smartbox: TemplateData can not be found''"
end
local paths = 0
for k,v in pairs( frame.args ) do
if data.params[k] then
data.params[k].path = v
paths = 1 + paths
end
end
if paths > 0 then
wikibase = require('Module:Wikibase')
end
local html = render(data, frame:getParent().args)
if not html then
return "''Smartbox: Content can not be rendered''"
end
return html
end
-- This little snippet will make itpossible to interactively
-- test the functions from the test console
local title = mw.title.getCurrentTitle()
if title:inNamespaces( 828, 829) then -- module and module discussion
Smartbox._ucFirst = ucFirst
Smartbox._replaceTerms = replaceTerms
Smartbox._escape = escape
Smartbox._parse = parse
Smartbox._merge = merge
Smartbox._report = report
Smartbox._usePreferredName = usePreferredName
Smartbox._useAlternateName = useAlternateName
Smartbox._useWikibaseLookup = useWikibaseLookup
Smartbox._useFallbackType = useFallbackType
Smartbox._render = render
Smartbox._compile = compile
end
-- export the accesspoint
return Smartbox