wolffd@0
|
1 function varargout = processArgs(userArgs, varargin)
|
wolffd@0
|
2 % Process function arguments, allowing args to be passed by
|
wolffd@0
|
3 % the user either as name/value pairs, or positionally, or both.
|
wolffd@0
|
4 %
|
wolffd@0
|
5 % This function also provides optional enforcement of required inputs, and
|
wolffd@0
|
6 % optional type checking. Argument names must start with the '-'
|
wolffd@0
|
7 % character and not precede any positional arguments.
|
wolffd@0
|
8 %
|
wolffd@0
|
9 % Matthew Dunham
|
wolffd@0
|
10 % University of British Columbia
|
wolffd@0
|
11 % Last updated January 14th, 2010
|
wolffd@0
|
12 %
|
wolffd@0
|
13 %% USAGE:
|
wolffd@0
|
14 %
|
wolffd@0
|
15 % [out1,out2,...,outN] = processArgs(userArgs ,...
|
wolffd@0
|
16 % '-name1' , default1 ,...
|
wolffd@0
|
17 % '-name2' , default2 ,...
|
wolffd@0
|
18 % '-nameN' , defaultN );
|
wolffd@0
|
19 %
|
wolffd@0
|
20 % The 'userArgs' input is a cell array and in normal usage is simply the
|
wolffd@0
|
21 % varargin cell array from the calling function. It contains 0 to N values
|
wolffd@0
|
22 % or 0 to N name/value pairs. It may also contain a combination of
|
wolffd@0
|
23 % positional and named arguments as long as no named argument precedes a
|
wolffd@0
|
24 % positional one.
|
wolffd@0
|
25 %
|
wolffd@0
|
26 % Note, this function is CASE INSENSITIVE.
|
wolffd@0
|
27 %
|
wolffd@0
|
28 %% ENFORCING REQUIRED ARGUMENTS
|
wolffd@0
|
29 %
|
wolffd@0
|
30 % To ensure that certain arguments are passed in, (and error if not), add a
|
wolffd@0
|
31 % '*' character to the corresponding name as in
|
wolffd@0
|
32 %
|
wolffd@0
|
33 % [out1,...] = processArgs(userArgs,'*-name1',default1,...
|
wolffd@0
|
34 %
|
wolffd@0
|
35 %
|
wolffd@0
|
36 % Providing empty values, (e.g. {},[],'') for required arguments also
|
wolffd@0
|
37 % errors unless these were explicitly passed as named arguments.
|
wolffd@0
|
38 %% TYPE CHECKING
|
wolffd@0
|
39 %
|
wolffd@0
|
40 % To enforce that the input type, (class) is the same type as the default
|
wolffd@0
|
41 % value, add a '+' character to the corresponding name as in
|
wolffd@0
|
42 %
|
wolffd@0
|
43 % [out1,...] = processArgs(userArgs,'+-name1',default1,...
|
wolffd@0
|
44 %
|
wolffd@0
|
45 % '+' and '*' can be combined as in
|
wolffd@0
|
46 %
|
wolffd@0
|
47 % [out1,...] = processArgs(userArgs,'*+-name1',default1,...
|
wolffd@0
|
48 %
|
wolffd@0
|
49 % or equivalently
|
wolffd@0
|
50 %
|
wolffd@0
|
51 % [out1,...] = processArgs(userArgs,'+*-name1',default1,...
|
wolffd@0
|
52 %% OTHER CONSIDERATIONS
|
wolffd@0
|
53 %
|
wolffd@0
|
54 % If the user passes in arguments in positional mode, and uses [], {}, or
|
wolffd@0
|
55 % '' as place holders, the default values are used in their place. When the
|
wolffd@0
|
56 % user passes in values via name/value pairs, this behavior does not
|
wolffd@0
|
57 % occur; the explicit value the user specified, (even if [], {}, '') is
|
wolffd@0
|
58 % always used.
|
wolffd@0
|
59 %
|
wolffd@0
|
60 %% ADVANCED
|
wolffd@0
|
61 % The programmer must specify the same number of output arguments as
|
wolffd@0
|
62 % possible input arguments, OR exactly one output. If exactly one output
|
wolffd@0
|
63 % is used, the outputs are all bundled into a single cell array and
|
wolffd@0
|
64 % returned with each arg value preceded by its name. This can be useful for
|
wolffd@0
|
65 % relaying some or all of the arguments to subsequent functions as is done
|
wolffd@0
|
66 % with the extractArgs function.
|
wolffd@0
|
67 %
|
wolffd@0
|
68 %% DEPENDENCIES
|
wolffd@0
|
69 %
|
wolffd@0
|
70 % None
|
wolffd@0
|
71 %
|
wolffd@0
|
72 %% EXAMPLES
|
wolffd@0
|
73 % These are all valid usages. Note that here the first and second arguments
|
wolffd@0
|
74 % are required and the types of the second and fourth arguments are
|
wolffd@0
|
75 % checked.
|
wolffd@0
|
76 %
|
wolffd@0
|
77 % outerFunction(obj,...
|
wolffd@0
|
78 % '-first' , 1 ,...
|
wolffd@0
|
79 % '-second' , MvnDist() ,...
|
wolffd@0
|
80 % '-third' , 22 ,...
|
wolffd@0
|
81 % '-fourth' , 10 );
|
wolffd@0
|
82 %
|
wolffd@0
|
83 % outerFunction(obj,'-fourth',3,'-second',MvnDist(),'-first',12);
|
wolffd@0
|
84 % outerFunction(obj,1,MvnDist(),3);
|
wolffd@0
|
85 % outerFunction(obj,1,MvnDist(),3,[]);
|
wolffd@0
|
86 % outerFunction(obj,'-first',1,'-second',DiscreteDist(),'-third',[]);
|
wolffd@0
|
87 % outerFunction(obj,1,MvnDist(),'-fourth',10);
|
wolffd@0
|
88 %
|
wolffd@0
|
89 %
|
wolffd@0
|
90 % function [a,b,c,d] = outerFunction(obj,varargin)
|
wolffd@0
|
91 % [a,b,c,d] = processArgs(varargin ,...
|
wolffd@0
|
92 % '*-first' , [] ,...
|
wolffd@0
|
93 % '*+-second' , MvnDist() ,...
|
wolffd@0
|
94 % '-third' , 18 ,...
|
wolffd@0
|
95 % '+-fourth' , 23 );
|
wolffd@0
|
96 % end
|
wolffd@0
|
97 %% CONSTANTS
|
wolffd@0
|
98 PREFIX = '-'; % prefix that must precede the names of arguments.
|
wolffd@0
|
99 REQ = '*'; % require the argument
|
wolffd@0
|
100 TYPE = '+'; % check the type of the arg against the default type
|
wolffd@0
|
101
|
wolffd@0
|
102 % set to true for more exhaustive error checking or false for faster
|
wolffd@0
|
103 % execution.
|
wolffd@0
|
104 FULL_ERROR_CHECK = true;
|
wolffd@0
|
105
|
wolffd@0
|
106 %% PROCESS VARARGIN - PASSED BY PROGRAMMER
|
wolffd@0
|
107
|
wolffd@0
|
108 %% Check Initial Inputs
|
wolffd@0
|
109 if ~iscell(userArgs) ,throwAsCaller(MException('PROCESSARGS:noUserArgs','PROGRAMMER ERROR - you must pass in the user''s arguments in a cell array as in processArgs(varargin,''-name'',val,...)'));end
|
wolffd@0
|
110 if isempty(varargin) ,throwAsCaller(MException('PROCESSARGS:emptyVarargin','PROGRAMMER ERROR - you have not passed in any name/default pairs to processArgs')); end
|
wolffd@0
|
111 %% Extract Programmer Argument Names and Markers
|
wolffd@0
|
112 progArgNames = varargin(1:2:end);
|
wolffd@0
|
113 maxNargs = numel(progArgNames);
|
wolffd@0
|
114 required = cellfun(@(c)any(REQ==c(1:min(3,end))),progArgNames);
|
wolffd@0
|
115 typecheck = cellfun(@(c)any(TYPE==c(1:min(3,end))),progArgNames);
|
wolffd@0
|
116 if ~iscellstr(progArgNames) ,throwAsCaller(MException('PROCESSARGS:notCellStr ',sprintf('PROGRAMMER ERROR - you must pass to processArgs name/default pairs'))); end
|
wolffd@0
|
117 %% Remove * and + Markers
|
wolffd@0
|
118 try
|
wolffd@0
|
119 progArgNames(required | typecheck) = ...
|
wolffd@0
|
120 cellfuncell(@(c)c(c~=REQ & c~=TYPE) ,...
|
wolffd@0
|
121 progArgNames(required | typecheck));
|
wolffd@0
|
122 catch ME
|
wolffd@0
|
123 if strcmp(ME.identifier,'MATLAB:UndefinedFunction')
|
wolffd@0
|
124 err = MException('PROCESSARGS:missingExternalFunctions','ProcessArgs requires the following external functions available in PMTK2: catString, cellfuncell, interweave, isprefix. Please add these to your MATLAB path.');
|
wolffd@0
|
125 throw(addCause(err,ME));
|
wolffd@0
|
126 else
|
wolffd@0
|
127 rethrow(ME);
|
wolffd@0
|
128 end
|
wolffd@0
|
129 end
|
wolffd@0
|
130 %% Set Default Values
|
wolffd@0
|
131 defaults = varargin(2:2:end);
|
wolffd@0
|
132 varargout = defaults;
|
wolffd@0
|
133 %% Check Programmer Supplied Arguments
|
wolffd@0
|
134 if mod(numel(varargin),2) ,throwAsCaller(MException('PROCESSARGS:oddNumArgs',sprintf('PROGRAMMER ERROR - you have passed in an odd number of arguments to processArgs, which requires name/default pairs'))); end
|
wolffd@0
|
135 if any(cellfun(@isempty,progArgNames)) ,throwAsCaller(MException('PROCESSARGS:emptyStrName ',sprintf('PROGRAMMER ERROR - empty-string names are not allowed')));end
|
wolffd@0
|
136 if nargout ~= 1 && nargout ~= maxNargs ,throwAsCaller(MException('PROCESSARGS:wrongNumOutputs',sprintf('PROGRAMMER ERROR - processArgs requires the same number of output arguments as named/default input pairs'))); end
|
wolffd@0
|
137 if ~isempty(PREFIX) && ...
|
wolffd@0
|
138 ~all(cellfun(@(c)~isempty(c) &&...
|
wolffd@0
|
139 c(1)==PREFIX,progArgNames))
|
wolffd@0
|
140 throwAsCaller(MException('PROCESSARGS:missingPrefix',sprintf('PROGRAMMER ERROR - processArgs requires that each argument name begin with the prefix %s',PREFIX)));
|
wolffd@0
|
141 end
|
wolffd@0
|
142 if FULL_ERROR_CHECK && ...
|
wolffd@0
|
143 numel(unique(progArgNames)) ~= numel(progArgNames) ,throwAsCaller(MException('PROCESSARGS:duplicateName',sprintf('PROGRAMMER ERROR - you can not use the same argument name twice')));end
|
wolffd@0
|
144 %% PROCESS USERARGS
|
wolffd@0
|
145
|
wolffd@0
|
146 %% Error Check User Args
|
wolffd@0
|
147 if numel(userArgs) == 0 && nargout > 1
|
wolffd@0
|
148 if any(required) ,throwAsCaller(MException('PROCESSARGS:missingReqArgs',sprintf('The following required arguments were not specified:\n%s',catString(progArgNames(required)))));
|
wolffd@0
|
149 else return;
|
wolffd@0
|
150 end
|
wolffd@0
|
151 end
|
wolffd@0
|
152 if FULL_ERROR_CHECK
|
wolffd@0
|
153 % slow, but helpful in transition from process_options to processArgs
|
wolffd@0
|
154 % checks for missing '-'
|
wolffd@0
|
155 if ~isempty(PREFIX)
|
wolffd@0
|
156 userstrings = lower(...
|
wolffd@0
|
157 userArgs(cellfun(@(c)ischar(c) && size(c,1)==1,userArgs)));
|
wolffd@0
|
158 problem = ismember(...
|
wolffd@0
|
159 userstrings,cellfuncell(@(c)c(2:end),progArgNames));
|
wolffd@0
|
160 if any(problem)
|
wolffd@0
|
161 if sum(problem) == 1, warning('processArgs:missingPrefix','The specified value ''%s'', matches an argument name, except for a missing prefix %s. It will be interpreted as a value, not a name.',userstrings{problem},PREFIX)
|
wolffd@0
|
162 else warning('processArgs:missingPrefix','The following values match an argument name, except for missing prefixes %s:\n\n%s\n\nThey will be interpreted as values, not names.',PREFIX,catString(userstrings(problem)));
|
wolffd@0
|
163 end
|
wolffd@0
|
164 end
|
wolffd@0
|
165 end
|
wolffd@0
|
166 end
|
wolffd@0
|
167 %% Find User Arg Names
|
wolffd@0
|
168 userArgNamesNDX = find(cellfun(@(c)ischar(c) &&...
|
wolffd@0
|
169 ~isempty(c) &&...
|
wolffd@0
|
170 c(1)==PREFIX,userArgs));
|
wolffd@0
|
171 %% Check User Arg Names
|
wolffd@0
|
172 if ~isempty(userArgNamesNDX) && ...
|
wolffd@0
|
173 ~isequal(userArgNamesNDX,userArgNamesNDX(1):2:numel(userArgs)-1)
|
wolffd@0
|
174 if isempty(PREFIX), throwAsCaller(MException('PROCESSARGS:missingVal',sprintf('\n(1) every named argument must be followed by its value\n(2) no positional argument may be used after the first named argument\n')));
|
wolffd@0
|
175 else throwAsCaller(MException('PROCESSARGS:posArgAfterNamedArg',sprintf('\n(1) every named argument must be followed by its value\n(2) no positional argument may be used after the first named argument\n(3) every argument name must begin with the ''%s'' character\n(4) values cannot be strings beginning with the %s character\n',PREFIX,PREFIX)));
|
wolffd@0
|
176 end
|
wolffd@0
|
177 end
|
wolffd@0
|
178 if FULL_ERROR_CHECK && ...
|
wolffd@0
|
179 ~isempty(userArgNamesNDX) && ...
|
wolffd@0
|
180 numel(unique(userArgs(userArgNamesNDX))) ~= numel(userArgNamesNDX)
|
wolffd@0
|
181 throwAsCaller(MException('PROCESSARGS:duplicateUserArg',sprintf('You have specified the same argument name twice')));
|
wolffd@0
|
182 end
|
wolffd@0
|
183 %% Extract Positional Args
|
wolffd@0
|
184 argsProvided = false(1,maxNargs);
|
wolffd@0
|
185 if isempty(userArgNamesNDX)
|
wolffd@0
|
186 positionalArgs = userArgs;
|
wolffd@0
|
187 elseif userArgNamesNDX(1) == 1
|
wolffd@0
|
188 positionalArgs = {};
|
wolffd@0
|
189 else
|
wolffd@0
|
190 positionalArgs = userArgs(1:userArgNamesNDX(1)-1);
|
wolffd@0
|
191 end
|
wolffd@0
|
192 %% Check For Too Many Inputs
|
wolffd@0
|
193 if numel(positionalArgs) + numel(userArgNamesNDX) > maxNargs ,throwAsCaller(MException('PROCESSARGS:tooManyInputs',sprintf('You have specified %d too many arguments to the function',numel(positionalArgs)+numel(userArgNamesNDX)- maxNargs)));end
|
wolffd@0
|
194 %% Process Positional Args
|
wolffd@0
|
195 for i=1:numel(positionalArgs)
|
wolffd@0
|
196 % don't overwrite default value if positional arg is
|
wolffd@0
|
197 % empty, i.e. '',{},[]
|
wolffd@0
|
198 if ~isempty(userArgs{i})
|
wolffd@0
|
199 argsProvided(i) = true;
|
wolffd@0
|
200 if typecheck(i) && ~isa(userArgs{i},class(defaults{i})) ,throwAsCaller(MException('PROCESSARGS:argWrongType',sprintf('Argument %d must be of type %s',i,class(defaults{i})))); end
|
wolffd@0
|
201 varargout{i} = userArgs{i};
|
wolffd@0
|
202 end
|
wolffd@0
|
203 end
|
wolffd@0
|
204 %% Process Named Args
|
wolffd@0
|
205 userArgNames = userArgs(userArgNamesNDX);
|
wolffd@0
|
206 userProgMap = zeros(1,numel(userArgNames));
|
wolffd@0
|
207 usedProgArgNames = false(1,numel(progArgNames));
|
wolffd@0
|
208 for i=1:numel(userArgNames)
|
wolffd@0
|
209 for j=1:numel(progArgNames)
|
wolffd@0
|
210 if ~usedProgArgNames(j) && strcmpi(userArgNames{i},progArgNames{j})
|
wolffd@0
|
211 userProgMap(i) = j;
|
wolffd@0
|
212 usedProgArgNames(j) = true;
|
wolffd@0
|
213 break;
|
wolffd@0
|
214 end
|
wolffd@0
|
215 end
|
wolffd@0
|
216 end
|
wolffd@0
|
217 %% Error Check User Args
|
wolffd@0
|
218 if any(~userProgMap) ,throwAsCaller(MException('PROCESSARGS:invalidArgNames',sprintf('The following argument names are invalid: %s',catString(userArgNames(userProgMap == 0),' , ')))); end
|
wolffd@0
|
219 if any(userProgMap <= numel(positionalArgs)) ,throwAsCaller(MException('PROCESSARGS:bothPosAndName' ,sprintf('You cannot specify an argument positionally, and by name in the same function call.')));end
|
wolffd@0
|
220 %% Extract User Values
|
wolffd@0
|
221 userValues = userArgs(userArgNamesNDX + 1);
|
wolffd@0
|
222 %% Type Check User Args
|
wolffd@0
|
223 if any(typecheck)
|
wolffd@0
|
224 for i=1:numel(userArgNamesNDX)
|
wolffd@0
|
225 if typecheck(userProgMap(i)) && ...
|
wolffd@0
|
226 ~isa(userArgs{userArgNamesNDX(i)+1},...
|
wolffd@0
|
227 class(defaults{userProgMap(i)}))
|
wolffd@0
|
228 throwAsCaller(MException('PROCESSARGS:wrongType',sprintf('Argument %s must be of type %s',userArgs{userArgNamesNDX(i)},class(defaults{userProgMap(i)}))));
|
wolffd@0
|
229 end
|
wolffd@0
|
230 end
|
wolffd@0
|
231 end
|
wolffd@0
|
232 varargout(userProgMap) = userValues;
|
wolffd@0
|
233 %% Check Required Args
|
wolffd@0
|
234 argsProvided(userProgMap) = true;
|
wolffd@0
|
235 if any(~argsProvided & required) ,throwAsCaller(MException('PROCESSARGS:emptyVals',sprintf('The following required arguments were either not specified, or were given empty values:\n%s',catString(progArgNames(~argsProvided & required))))); end
|
wolffd@0
|
236 %% Relay Mode
|
wolffd@0
|
237 if nargout == 1 && (numel(varargin) > 2 || (numel(varargin) == 1 && isprefix('-',varargin{1})))
|
wolffd@0
|
238 varargout = {interweave(progArgNames,varargout)};
|
wolffd@0
|
239 end
|
wolffd@0
|
240
|
wolffd@0
|
241 end
|
wolffd@0
|
242
|
wolffd@0
|
243
|
wolffd@0
|
244 function s = catString(c,delim)
|
wolffd@0
|
245 % Converts a cell array of strings to a single string, (i.e. single-row
|
wolffd@0
|
246 % character array). The specified delimiter, delim, is added between each
|
wolffd@0
|
247 % entry. Include any spaces you want in delim. If delim is not specified,
|
wolffd@0
|
248 % ', ' is used instead. If c is already a string, it is just returned. If c
|
wolffd@0
|
249 % is empty, s = ''.
|
wolffd@0
|
250 %
|
wolffd@0
|
251 % EXAMPLE:
|
wolffd@0
|
252 %
|
wolffd@0
|
253 % s = catString({'touch /tmp/foo';'touch /tmp foo2';'mkdir /tmp/test'},' && ')
|
wolffd@0
|
254 % s =
|
wolffd@0
|
255 % touch /tmp/foo && touch /tmp foo2 && mkdir /tmp/test
|
wolffd@0
|
256
|
wolffd@0
|
257 if nargin == 0; s = ''; return; end
|
wolffd@0
|
258 if ischar(c), s=c;
|
wolffd@0
|
259 if strcmp(s,','),s = '';end
|
wolffd@0
|
260 return;
|
wolffd@0
|
261 end
|
wolffd@0
|
262 if isempty(c),s=''; return;end
|
wolffd@0
|
263 if nargin < 2, delim = ', '; end
|
wolffd@0
|
264 s = '';
|
wolffd@0
|
265 for i=1:numel(c)
|
wolffd@0
|
266 s = [s, rowvec(c{i}),rowvec(delim)]; %#ok
|
wolffd@0
|
267 end
|
wolffd@0
|
268 s(end-numel(delim)+1:end) = [];
|
wolffd@0
|
269 if strcmp(s,','),s = '';end
|
wolffd@0
|
270 end
|
wolffd@0
|
271
|
wolffd@0
|
272
|
wolffd@0
|
273 function out = cellfuncell(fun, C, varargin)
|
wolffd@0
|
274 out = cellfun(fun, C, varargin{:},'UniformOutput',false);
|
wolffd@0
|
275 end
|
wolffd@0
|
276
|
wolffd@0
|
277 function C = interweave(A,B)
|
wolffd@0
|
278 % If A, B are two cell arrays of length N1, N2, C is a cell array of length
|
wolffd@0
|
279 % N1 + N2 where where C(1) = A(1), C(2) = B(1), C(3) = A(2), C(4) = B(2), ... etc
|
wolffd@0
|
280 % Note, C is always a row vector. If one cell array is longer than the
|
wolffd@0
|
281 % other the remaining elements of the longer cell array are added to the
|
wolffd@0
|
282 % end of C. A and B are first converted to column vectors.
|
wolffd@0
|
283 A = A(:); B = B(:);
|
wolffd@0
|
284 C = cell(length(A)+length(B),1);
|
wolffd@0
|
285 counter = 1;
|
wolffd@0
|
286 while true
|
wolffd@0
|
287 if ~isempty(A)
|
wolffd@0
|
288 C(counter) = A(1); A(1) = [];
|
wolffd@0
|
289 counter = counter + 1;
|
wolffd@0
|
290 end
|
wolffd@0
|
291 if ~isempty(B)
|
wolffd@0
|
292 C(counter) = B(1); B(1) = [];
|
wolffd@0
|
293 counter = counter + 1;
|
wolffd@0
|
294 end
|
wolffd@0
|
295 if isempty(A) && isempty(B)
|
wolffd@0
|
296 break;
|
wolffd@0
|
297 end
|
wolffd@0
|
298 end
|
wolffd@0
|
299 C = C';
|
wolffd@0
|
300 end
|
wolffd@0
|
301
|
wolffd@0
|
302 function p = isprefix(short,long)
|
wolffd@0
|
303 % ISPREFIX Tests if the first arg is a prefix of the second.
|
wolffd@0
|
304 % The second arg may also be a cell array of strings, in which case, each
|
wolffd@0
|
305 % is tested. CASE SENSITIVE!
|
wolffd@0
|
306 %
|
wolffd@0
|
307 % If the second argument is not a string, p = false, it does not error.
|
wolffd@0
|
308 %
|
wolffd@0
|
309 % EXAMPLES:
|
wolffd@0
|
310 %
|
wolffd@0
|
311 % isprefix('foo','foobar')
|
wolffd@0
|
312 % ans =
|
wolffd@0
|
313 % 1
|
wolffd@0
|
314 %
|
wolffd@0
|
315 %isprefix('test_',{'test_MvnDist','test_DiscreteDist','UnitTest'})
|
wolffd@0
|
316 %ans =
|
wolffd@0
|
317 % 1 1 0
|
wolffd@0
|
318 error(nargchk(2,2,nargin));
|
wolffd@0
|
319 if ischar(long)
|
wolffd@0
|
320 p = strncmp(long,short,length(short));
|
wolffd@0
|
321 elseif iscell(long)
|
wolffd@0
|
322 p = cellfun(@(c)isprefix(short,c),long);
|
wolffd@0
|
323 else
|
wolffd@0
|
324 p = false;
|
wolffd@0
|
325 end
|
wolffd@0
|
326 end
|
wolffd@0
|
327
|
wolffd@0
|
328
|
wolffd@0
|
329 function x = rowvec(x)
|
wolffd@0
|
330 x = x(:)';
|
wolffd@0
|
331 end
|
wolffd@0
|
332
|
wolffd@0
|
333 function x = colvec(x)
|
wolffd@0
|
334 x = x(:);
|
wolffd@0
|
335 end
|
wolffd@0
|
336
|
wolffd@0
|
337
|
wolffd@0
|
338
|
wolffd@0
|
339
|
wolffd@0
|
340
|
wolffd@0
|
341
|
wolffd@0
|
342
|
wolffd@0
|
343
|
wolffd@0
|
344
|
wolffd@0
|
345
|
wolffd@0
|
346
|