Modul:Smartbox/parsers
--- Class for validators for infobox entries
local mt = {}
local Validator = setmetatable( {}, mt )
--- The local language
local contentLanguage = mw.getContentLanguage()
--- Lookup of missing class members
function Validator:__index( key ) -- luacheck: no self
return Validator[key]
end
local validators = {
--- The parsers for number type
['number'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-number', { nil }
end
},
-- single number
{
'^%s*[-+,.%d]+%s*$',
function( str, params )
local num = contentLanguage:parseFormattedNumber( mw.text.trim( str ) )
if not num then
return false, 'failed-number', { nil }
end
local formatted = params.format and mw.ustring.format( params.format, num ) or nil
return true, 'single-number',
{
formatted or mw.message.numParam( num )
}
end
},
-- number with a prefix
{
'^%s*%D+[-+,.%d]+%s*$',
function( str, params )
local prefix, value = str:match( '^(%s*%D+)([-+,.%d]+%s*)$' )
local num = contentLanguage:parseFormattedNumber( mw.text.trim( value ) )
if not num then
return false, 'failed-number', { nil }
end
local formatted = params.format and mw.ustring.format( params.format, num ) or nil
local prefixed = mw.text.trim( prefix )
return true, 'prefix-number',
{
Units and replaceTerms( mw.text.nowiki( prefixed ), Units ) or prefixed,
formatted or mw.message.numParam( num )
}
end
},
-- number with a suffix
{
'^%s*[-+,.%d]+%D-%s*$', -- this can hit a string with only trailing blanks
function( str, params )
local value, suffix = str:match( '^(%s*[-+,.%d]+)(%D-%s*)$' )
local num = contentLanguage:parseFormattedNumber( mw.text.trim( value ) )
if not num then
return false, 'failed-number', { nil }
end
local formatted = params.format and mw.ustring.format( params.format, num ) or nil
local suffixed = mw.text.trim( suffix )
return true, 'suffix-number',
{
formatted or mw.message.numParam( num ),
Units and replaceTerms( mw.text.nowiki( suffixed ), Units ) or suffixed,
}
end
},
-- number with circumfix
{
'^%s*%D+[-+,.%d]+%D-%s*$',
function( str, params )
local prefix, value, suffix = str:match( '^(%s*%D+)([-+,.%d]+)(%D-%s*)$' )
local num = contentLanguage:parseFormattedNumber( mw.text.trim( value ) )
if not num then
return false, 'failed-number', { nil }
end
local formatted = params.format and mw.ustring.format( params.format, num ) or nil
local prefixed = mw.text.trim( prefix )
local suffixed = mw.text.trim( suffix )
return true, 'circumfix-number',
{
Units and replaceTerms( mw.text.nowiki( prefixed ), Units ) or prefixed,
formatted or mw.message.numParam( num ),
Units and replaceTerms( mw.text.nowiki( suffixed ), Units ) or suffixed,
}
end
},
-- catch all
{
'^.*$',
function( str, params )
return false, 'failed-number', { nil }
end
}
},
--- The parsers for string type
['string'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-string', { nil }
end
},
-- single value
{
'^%s*%S.*$',
function( str, params )
local text = mw.text.trim( str )
return true, 'single-string', { mw.text.nowiki( text ) }
end
}
},
--- The parsers for unknown type
['unknown'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-unknown', { nil }
end
},
-- single value
{
'^%s*%S.*$',
function( str, params )
local text = mw.text.trim( str )
-- As 'unknown' this should be escaped per def.
return true, 'single-unknown', { mw.text.nowiki( text ) }
end
}
},
--- The parsers for content type
['content'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-content', { nil }
end
},
-- single value
{
'^%s*%S.*$',
function( str, params )
local text = mw.text.trim( str )
return true, 'single-content', { text }
end
}
},
--- The parsers for line type
['line'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-line', { nil }
end
},
-- single value
{
'^%s*%S.*$',
function( str, params )
local text = mw.text.trim( str )
return true, 'single-line', { text }
end
}
},
--- The parsers for boolean type
['boolean'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-boolean', { nil }
end
},
-- single value
{
'^%s*%d%s*$',
function( str, params )
local num = tonumber( mw.text.trim( str ) )
if not num then
return false, 'failed-boolean', { nil }
end
return true, 'single-boolean', { mw.message.numParam( num ) }
end
},
-- catch all
{
'^.*$',
function( str, params )
return false, 'failed-boolean', { nil }
end
}
},
--- The parsers for wiki-user-name type
['wiki-user-name'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-wiki-user-name', { nil }
end
},
-- single value
{
'^%s*%S.*%s*$',
function( str, params )
local text = mw.text.trim( str )
local title = mw.title.new( text, 'user' )
if not title then
return false, 'failed-wiki-user-name', { nil }
end
if not title:inNamespace( 'user' ) then
return false, 'failed-wiki-user-name', { nil }
end
if title.isSubpage then
return false, 'failed-wiki-user-name', { nil }
end
return true, 'single-wiki-user-name', { text }
end
}
},
--- The parsers for wiki-page-name type
['wiki-page-name'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-wiki-page-name', { nil }
end
},
-- single value
{
'^%s*%S.*%s*$',
function( str, params )
local text = mw.text.trim( str )
local title = mw.title.new( text )
if not title then
return false, 'failed-wiki-page-name', { nil }
end
return true, 'single-wiki-page-name', { text }
end
}
},
--- The parsers for wiki-file-name type
['wiki-file-name'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-wiki-file-name', { nil }
end
},
-- single value
{
'^%s*%S.*%s*$',
function( str, params )
local text = mw.text.trim( str )
local title = mw.title.new( text, 'file' )
if not title then
return false, 'failed-wiki-file-name', { nil }
end
if not title:inNamespace( 'file' ) then
return false, 'failed-wiki-file-name', { nil }
end
if title.isSubpage then
return false, 'failed-wiki-file-name', { nil }
end
return true, 'single-wiki-file-name', { text }
end
}
},
--- The parsers for wiki-template-name type
['wiki-template-name'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-wiki-template-name', { nil }
end
},
-- single value
{
'^%s*%S.*%s*$',
function( str, params )
local text = mw.text.trim( str )
local title = mw.title.new( text, 'template' )
if not title then
return false, 'failed-wiki-template-name', { nil }
end
if not title:inNamespace( 'template' ) then
return false, 'failed-wiki-template-name', { nil }
end
return true, 'single-wiki-template-name', { text }
end
}
},
--- The parsers for url type
['url'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-url', { nil }
end
},
-- single type
{
'^%s*%S.*$',
function( str, params )
local text = mw.text.trim( str )
local uri = mw.uri.new( text )
if not uri then
return false, 'failed-url', { nil }
end
local valid = mw.uri.validate( uri )
if valid ~= true then
return false, 'failed-url', { nil }
end
return true, 'single-url', { text }
end
}
},
--- The parsers for date type
['date'] = {
-- empty value
{
'^%s*$',
function( str, params )
return true, 'empty-date', { nil }
end
},
-- single value
{
'^%s*%S.*%s*$',
function( str, params )
local date = contentLanguage:formatDate( params.format or 'Y-m-d"T"H:i:s', mw.text.trim( str ) )
if not date then
return false, 'failed-date', { nil }
end
return true, 'single-date', { date }
end
}
}
}
--- Create a new instance
function Validator.create( ... )
local self = setmetatable( {}, Validator )
self:_init( ... )
return self
end
--- Initialize a new instance
function Validator:_init( ... )
self.validators = {}
-- Save the arguments
for k,list in pairs( { ... } ) do
self.validators[k] = {}
for i,v in ipairs( list ) do
self.validators[k][i] = v
end
end
-- Append the defaults
for k,list in pairs( validators ) do
if not self.validators[k] then
self.validators[k] = {}
end
for i,v in ipairs( list ) do
self.validators[k][i] = v
end
end
return self
end
--- Bind the units translation table
function mt.__call( tbl, units )
tbl.units = units
return tbl
end
--- The core parser before dispatching
function Validator:parse( str, params, lang )
if self.validators[params.type] then
for _,v in ipairs( self.validators[params.type] ) do
if str:match( v[1] ) then
local keys = {}
local success, 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 success, msg:params( unpack( parts ) ):plain(), parts
end
end
end
return false, str
end
--- Replace terms with preformatted strings
local function replaceTerms( str, validator )
if not validator then
validator = {}
end
if not validator.units then
return str
end
local tmp,_ = string.gsub( str, '(%S+)', function( part ) return validator.units[part] or part end)
return tmp
end
-- This little snippet will make it possible to interactively
-- test the functions from the test console
local title = mw.title.getCurrentTitle()
if title:inNamespaces( 828, 829) then -- module and module discussion
Validator._replaceTerms = replaceTerms
end
return Validator