Modul:Smartbox

Fra Wikipedia, den frie encyklopedi
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