function argStruct = parse_args(varargin)
%
% parse_args.m--Parses input arguments for a client function. Arguments are
% assumed to be in parameter/value pair form.
%
% The following example shows the steps involved in using parse_args.m:
%
% (1) Create m-file "myfunc.m" with the following function declaration:
% function [] = myfunc(varargin)
%
% (2) Inside myfunc.m, include the following lines:
% defaultVals.figureWidth = 0.8;
% defaultVals.figureHeight = 0.5;
% argStruct = parse_args(defaultVals,varargin{:});
%
% (3) Call your function with the following syntax:
% myfunc('figureHeight',0.99)
%
% Inside myfunc.m, the structured variable argStruct will contain fields
% named 'figureWidth' and 'figureHeight'. The 'figureWidth' field will
% contain the default value of 0.8, since no non-default value was passed
% to myfunc.m, but the 'figureHeight' field will contain the value 0.99,
% over-riding the default value of 0.5.
%
% There are variations on this pattern:
%
% (a) Your "myfunc.m" program may take arguments in addition to varargin,
% but they must precede varargin. For example, your function
% declaration could look like this:
% function [] = myfunc(numFiles,pathName,varargin)
%
% (b) It is not necessary to declare any default values. Your "myfunc.m"
% program can call parse_args.m like this:
% argStruct = parse_args(varargin{:});
%
% (c) You can call myfunc.m with all parameter/value pairs specified, none
% of them specified, or anything in between. In the example above,
% myfunc.m could be called like this:
% myfunc('figureHeight',0.99,'figureWidth',0.2);
% or like this:
% myfunc;
%
% (d) Your "myfunc.m" program can call parse_args.m with two additional
% arguments:
% argStruct = parse_args(...,allowNewFields,isCaseSensitive);
% where allowNewFields and isCaseSensitive are both Boolean values. By
% default, isCaseSensitive is true, so parse_args.m will treat the
% parameters 'lineColour' and 'linecolour' (for example) as different
% parameters. The allowNewFields parameter is also true by default,
% meaning that parse_args.m will accept parameters whose names are NOT
% included as fields in the default values structured variable. You can
% over-ride the default behaviour by specifying a different value for
% allowNewFields or isCaseSensitive, but in this case, BOTH of these
% arguments must be specified in the call to parse_args.m as shown
% above.
%
% N.B., program originally named parvalpairs.m. Program is based on the
% (University of Hawaii) Firing Group's fillstruct.m.
%
% Syntax: argStruct = parse_args(<defaultStruct>,par1,val1,par2,val2,...,<allowNewFields,isCaseSensitive>)
%
% e.g., argStruct = parse_args('Position',[256 308 512 384],'Units','pixels','Color',[1 0 1])
%
% e.g., defaultStruct.a = pi;
% defaultStruct.b = 'hello';
% defaultStruct.c = [1;2;3];
% allowNewFields = 1;
% isCaseSensitive = 0;
% argStruct = parse_args(defaultStruct,varargin{:},allowNewFields,isCaseSensitive);
% % N.B., for demonstration on command line (where you won't have a
% varargin variable), use this syntax:
% argStruct = parse_args(defaultStruct,'A',pi/2,'b','bye','z','new field',allowNewFields,isCaseSensitive)
% Developed in Matlab 6.1.0.450 (R12.1) on Linux. Kevin
% Bartlett(kpb@hawaii.edu), 2003/04/08, 11:49
%--------------------------------------------------------------------------
% Handle input arguments.
args = varargin;
if nargin == 0
argStruct = struct([]);
return;
end % if
% If a structured variable containing default field values has been
% supplied, separate it from the other input arguments.
defaultStruct = struct([]);
if isstruct(args{1})
defaultStruct = args{1};
if length(args) > 1
args = args(2:end);
else
args = {};
end % if
end % if
argStruct = defaultStruct;
% Determine if values of the variables "allowNewFields" and
% "isCaseSensitive" have been specified.
% ...Default values:
allowNewFields = 1;
isCaseSensitive = 1;
if length(args) > 1
% If no values of allowNewFields and isCaseSensitive have been
% specified, then all the remaining arguments will be parameter/value
% pairs. The second-to-last argument should then be a string.
if ~isstr(args{end-1})
% The second-to-last argument is NOT a string, so it must be the
% Boolean variable allowNewFields.
allowNewFields = args{end-1};
% ...and the last input argument is the Boolean variable
% isCaseSensitive.
isCaseSensitive = args{end};
if length(args) > 1
args = args(1:end-2);
else
args = {};
end % if
end % if
end % if
% If no arguments remain after extracting the default field values and the
% Boolean variables "allowNewFields" and "isCaseSensitive", then exit now.
% The value of argStruct returned will contain the same values as
% defaultStruct.
if length(args) == 0
return;
end % if
if ~ismember(allowNewFields,[1 0])
error([mfilename '.m--Value for "allowNewFields" must be 1 or 0.']);
end % if
if ~ismember(isCaseSensitive,[1 0])
error([mfilename '.m--Value for "isCaseSensitive" must be 1 or 0.']);
end % if
% Remaining input arguments should be parameter/value pairs.
existingFieldNames = fieldnames(defaultStruct);
lowerExistingFieldNames = lower(existingFieldNames);
for iArg = 1:2:length(args)
thisFieldName = args{iArg};
if ~isstr(thisFieldName)
error([mfilename '.m--Parameter names must be strings.']);
end % if
thisField = args{iArg+1};
% Find out if field already exists.
if isCaseSensitive == 1
if ismember(thisFieldName,existingFieldNames)
fieldExists = 1;
fieldNameToInsert = thisFieldName;
else
fieldExists = 0;
fieldNameToInsert = thisFieldName;
end % if
else
if ismember(lower(thisFieldName),lowerExistingFieldNames)
fieldExists = 1;
matchIndex = strmatch(lower(thisFieldName),lowerExistingFieldNames,'exact');
fieldNameToInsert = existingFieldNames{matchIndex};
else
fieldExists = 0;
fieldNameToInsert = thisFieldName;
end % if
end % if
% If new fields are not permitted to be added, test that field already exists.
if allowNewFields == 0 && fieldExists == 0
% If the default structure is empty, and the user is not permitting
% the addition of new fields, there is no point in running this
% program; probably the user doesn't intend this.
if isempty(defaultStruct)
error([mfilename '.m--Need to permit the addition of new fields if no default structure specified.']);
end % if
if isCaseSensitive == 1
%error([mfilename '.m--Attempt to add new parameter to existing set (case-sensitive). Use allowNewFields=1 to allow this.']);
error([mfilename '.m--Unrecognised input argument ''' thisFieldName '''. (arguments are case-sensitive).']);
else
%error([mfilename '.m--Attempt to add new parameter to existing set. Use allowNewFields=1 to allow this.']);
error([mfilename '.m--Unrecognised input argument ''' thisFieldName '''.']);
end % if
end % if
if isempty(argStruct)
argStruct = struct(fieldNameToInsert,thisField);
else
argStruct.(fieldNameToInsert) = thisField;
end % if
end % for