Modul:Smartbox/parsers

Fra Wikipedia, den frie encyklopedi
Moduldokumentasjon


--- 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