% FINPUTCHECK - check Matlab function {'key','value'} input argument pairs
%
% Usage: >> result = finputcheck( varargin, fieldlist );
% >> [result varargin] = finputcheck( varargin, fieldlist, ...
% callingfunc, mode, verbose );
% Input:
% varargin - Cell array 'varargin' argument from a function call using 'key',
% 'value' argument pairs. See Matlab function 'varargin'.
% May also be a structure such as struct(varargin{:})
% fieldlist - A 4-column cell array, one row per 'key'. The first
% column contains the key string, the second its type(s),
% the third the accepted value range, and the fourth the
% default value. Allowed types are 'boolean', 'integer',
% 'real', 'string', 'cell' or 'struct'. For example,
% {'key1' 'string' { 'string1' 'string2' } 'defaultval_key1'}
% {'key2' {'real' 'integer'} { minint maxint } 'defaultval_key2'}
% callingfunc - Calling function name for error messages. {default: none}.
% mode - ['ignore'|'error'] ignore keywords that are either not specified
% in the fieldlist cell array or generate an error.
% {default: 'error'}.
% verbose - ['verbose', 'quiet'] print information. Default: 'verbose'.
%
% Outputs:
% result - If no error, structure with 'key' as fields and 'value' as
% content. If error this output contain the string error.
% varargin - residual varagin containing unrecognized input arguments.
% Requires mode 'ignore' above.
%
% Note: In case of error, a string is returned containing the error message
% instead of a structure.
%
% Example (insert the following at the beginning of your function):
% result = finputcheck(varargin, ...
% { 'title' 'string' [] ''; ...
% 'percent' 'real' [0 1] 1 ; ...
% 'elecamp' 'integer' [1:10] [] });
% if ischar(result)
% error(result);
% end
%
% Note:
% The 'title' argument should be a string. {no default value}
% The 'percent' argument should be a real number between 0 and 1. {default: 1}
% The 'elecamp' argument should be an integer between 1 and 10 (inclusive).
%
% Now 'g.title' will contain the title arg (if any, else the default ''), etc.
%
% Author: Arnaud Delorme, CNL / Salk Institute, 10 July 2002
% Copyright (C) Arnaud Delorme, CNL / Salk Institute, 10 July 2002, arno@salk.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.
function [g, varargnew] = finputcheck( vararg, fieldlist, callfunc, mode, verbose )
if nargin < 2
help finputcheck;
return;
end
if nargin < 3
callfunc = '';
else
callfunc = [callfunc ' ' ];
end
if nargin < 4
mode = 'do not ignore';
end
if nargin < 5
verbose = 'verbose';
end
NAME = 1;
TYPE = 2;
VALS = 3;
DEF = 4;
SIZE = 5;
varargnew = {};
% create structure
% ----------------
if ~isempty(vararg)
if isstruct(vararg)
g = vararg;
else
for index=1:length(vararg)
if iscell(vararg{index})
vararg{index} = {vararg{index}};
end
end
try
g = struct(vararg{:});
catch
vararg = removedup(vararg, verbose);
try
g = struct(vararg{:});
catch
g = [ callfunc 'error: bad ''key'', ''val'' sequence' ]; return;
end
end
end
else
g = [];
end
for index = 1:size(fieldlist,NAME)
% check if present
% ----------------
if ~isfield(g, fieldlist{index, NAME})
g = setfield( g, fieldlist{index, NAME}, fieldlist{index, DEF});
end
tmpval = getfield( g, {1}, fieldlist{index, NAME});
% check type
% ----------
if ~iscell( fieldlist{index, TYPE} )
res = fieldtest( fieldlist{index, NAME}, fieldlist{index, TYPE}, ...
fieldlist{index, VALS}, tmpval, callfunc );
if ischar(res), g = res; return; end
else
testres = 0;
tmplist = fieldlist;
for it = 1:length( fieldlist{index, TYPE} )
if ~iscell(fieldlist{index, VALS})
res{it} = fieldtest( fieldlist{index, NAME}, fieldlist{index, TYPE}{it}, ...
fieldlist{index, VALS}, tmpval, callfunc );
else res{it} = fieldtest( fieldlist{index, NAME}, fieldlist{index, TYPE}{it}, ...
fieldlist{index, VALS}{it}, tmpval, callfunc );
end
if ~ischar(res{it}), testres = 1; end
end
if testres == 0
g = res{1};
for tmpi = 2:length(res)
g = [ g 10 'or ' res{tmpi} ];
end
return;
end
end
end
% check if fields are defined
% ---------------------------
allfields = fieldnames(g);
for index=1:length(allfields)
if isempty(strmatch(allfields{index}, fieldlist(:, 1)', 'exact'))
if ~strcmpi(mode, 'ignore')
g = [ callfunc 'error: undefined argument ''' allfields{index} '''']; return;
end
varargnew{end+1} = allfields{index};
varargnew{end+1} = getfield(g, {1}, allfields{index});
end
end
function g = fieldtest( fieldname, fieldtype, fieldval, tmpval, callfunc )
NAME = 1;
TYPE = 2;
VALS = 3;
DEF = 4;
SIZE = 5;
g = [];
if isstring2(tmpval)
tmpval = char(tmpval);
end
switch fieldtype
case { 'integer' 'real' 'boolean' 'float' }
if ~isnumeric(tmpval) && ~islogical(tmpval)
g = [ callfunc 'error: argument ''' fieldname ''' must be numeric' ]; return;
end
if strcmpi(fieldtype, 'boolean')
if tmpval ~=0 && tmpval ~= 1
g = [ callfunc 'error: argument ''' fieldname ''' must be 0 or 1' ]; return;
end;
else
if strcmpi(fieldtype, 'integer')
if ~isempty(fieldval)
if (any(isnan(tmpval(:))) && ~any(isnan(fieldval))) ...
&& (~ismember(tmpval, fieldval))
g = [ callfunc 'error: wrong value for argument ''' fieldname '''' ]; return;
end
end
else % real or float
if ~isempty(fieldval) && ~isempty(tmpval)
if any(tmpval < fieldval(1)) || any(tmpval > fieldval(2))
g = [ callfunc 'error: value out of range for argument ''' fieldname '''' ]; return;
end
end
end
end
case 'string'
if ~ischar(tmpval) && ~isempty(tmpval)
g = [ callfunc 'error: argument ''' fieldname ''' must be a string' ]; return;
end
if ~isempty(fieldval)
if isempty(strmatch(char(lower(tmpval)), lower(fieldval), 'exact'))
g = [ callfunc 'error: wrong value for argument ''' fieldname '''' ]; return;
end
end
case 'cell'
if ~iscell(tmpval)
g = [ callfunc 'error: argument ''' fieldname ''' must be a cell array' ]; return;
end
case 'struct'
if ~isstruct(tmpval)
g = [ callfunc 'error: argument ''' fieldname ''' must be a structure' ]; return;
end
case 'function_handle'
if ~isa(tmpval, 'function_handle')
g = [ callfunc 'error: argument ''' fieldname ''' must be a function handle' ]; return;
end
case '';
otherwise, error([ 'finputcheck error: unrecognized type ''' fieldname '''' ]);
end
% remove duplicates in the list of parameters
% -------------------------------------------
function cella = removedup(cella, verbose)
% make sure if all the values passed to UNIQUE are strings, if not, exist
%try
allFields = cella(1:2:end);
[tmp, indices, X] = unique_bc(allFields);
if length(tmp) ~= length(allFields)
Y = hist(X,unique(X));
fieldDuplicates = allFields(Y > 1);
myfprintf(verbose,'Note: duplicate ''%s'' parameter(s), keeping the last one(s)\n', fieldDuplicates{1});
end
cella = cella(sort(union(indices*2-1, indices*2)));
%catch
% some elements of cella were not string
% error('some ''key'' values are not string.');
%end;
function myfprintf(verbose, varargin)
if strcmpi(verbose, 'verbose')
fprintf(varargin{:});
end
function b = isstring2(str)
b = false;
if exist('isstring', 'builtin')
b = isstring(str);
end