1% Licensed to the Apache Software Foundation (ASF) under one
2% or more contributor license agreements.  See the NOTICE file
3% distributed with this work for additional information
4% regarding copyright ownership.  The ASF licenses this file
5% to you under the Apache License, Version 2.0 (the
6% "License"); you may not use this file except in compliance
7% with the License.  You may obtain a copy of the License at
8%
9%   http://www.apache.org/licenses/LICENSE-2.0
10%
11% Unless required by applicable law or agreed to in writing,
12% software distributed under the License is distributed on an
13% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14% KIND, either express or implied.  See the License for the
15% specific language governing permissions and limitations
16% under the License.
17
18function data = parse_json(fname,varargin)
19%PARSE_JSON parse a JSON (JavaScript Object Notation) file or string
20%
21% Based on jsonlab (https://github.com/fangq/jsonlab) created by Qianqian Fang. Jsonlab is lisonced under BSD or GPL v3.
22
23global pos inStr len  esc index_esc len_esc isoct arraytoken
24
25if(regexp(fname,'^\s*(?:\[.+\])|(?:\{.+\})\s*$','once'))
26   string=fname;
27elseif(exist(fname,'file'))
28   try
29       string = fileread(fname);
30   catch
31       try
32           string = urlread(['file://',fname]);
33       catch
34           string = urlread(['file://',fullfile(pwd,fname)]);
35       end
36   end
37else
38   error('input file does not exist');
39end
40
41pos = 1; len = length(string); inStr = string;
42isoct=exist('OCTAVE_VERSION','builtin');
43arraytoken=find(inStr=='[' | inStr==']' | inStr=='"');
44jstr=regexprep(inStr,'\\\\','  ');
45escquote=regexp(jstr,'\\"');
46arraytoken=sort([arraytoken escquote]);
47
48% String delimiters and escape chars identified to improve speed:
49esc = find(inStr=='"' | inStr=='\' ); % comparable to: regexp(inStr, '["\\]');
50index_esc = 1; len_esc = length(esc);
51
52opt=varargin2struct(varargin{:});
53
54if(jsonopt('ShowProgress',0,opt)==1)
55    opt.progressbar_=waitbar(0,'loading ...');
56end
57jsoncount=1;
58while pos <= len
59    switch(next_char)
60        case '{'
61            data{jsoncount} = parse_object(opt);
62        case '['
63            data{jsoncount} = parse_array(opt);
64        otherwise
65            error_pos('Outer level structure must be an object or an array');
66    end
67    jsoncount=jsoncount+1;
68end % while
69
70jsoncount=length(data);
71if(jsoncount==1 && iscell(data))
72    data=data{1};
73end
74
75if(isfield(opt,'progressbar_'))
76    close(opt.progressbar_);
77end
78
79%%-------------------------------------------------------------------------
80function object = parse_object(varargin)
81    parse_char('{');
82    object = [];
83    if next_char ~= '}'
84        while 1
85            str = parseStr(varargin{:});
86            if isempty(str)
87                error_pos('Name of value at position %d cannot be empty');
88            end
89            parse_char(':');
90            val = parse_value(varargin{:});
91            object.(valid_field(str))=val;
92            if next_char == '}'
93                break;
94            end
95            parse_char(',');
96        end
97    end
98    parse_char('}');
99    if(isstruct(object))
100        object=struct2jdata(object);
101    end
102
103%%-------------------------------------------------------------------------
104
105function object = parse_array(varargin) % JSON array is written in row-major order
106global pos inStr isoct
107    parse_char('[');
108    object = cell(0, 1);
109    dim2=[];
110    arraydepth=jsonopt('JSONLAB_ArrayDepth_',1,varargin{:});
111    pbar=-1;
112    if(isfield(varargin{1},'progressbar_'))
113        pbar=varargin{1}.progressbar_;
114    end
115
116    if next_char ~= ']'
117	if(jsonopt('FastArrayParser',1,varargin{:})>=1 && arraydepth>=jsonopt('FastArrayParser',1,varargin{:}))
118            [endpos, e1l, e1r]=matching_bracket(inStr,pos);
119            arraystr=['[' inStr(pos:endpos)];
120            arraystr=regexprep(arraystr,'"_NaN_"','NaN');
121            arraystr=regexprep(arraystr,'"([-+]*)_Inf_"','$1Inf');
122            arraystr(arraystr==sprintf('\n'))=[];
123            arraystr(arraystr==sprintf('\r'))=[];
124            %arraystr=regexprep(arraystr,'\s*,',','); % this is slow,sometimes needed
125            if(~isempty(e1l) && ~isempty(e1r)) % the array is in 2D or higher D
126        	astr=inStr((e1l+1):(e1r-1));
127        	astr=regexprep(astr,'"_NaN_"','NaN');
128        	astr=regexprep(astr,'"([-+]*)_Inf_"','$1Inf');
129        	astr(astr==sprintf('\n'))=[];
130        	astr(astr==sprintf('\r'))=[];
131        	astr(astr==' ')='';
132        	if(isempty(find(astr=='[', 1))) % array is 2D
133                    dim2=length(sscanf(astr,'%f,',[1 inf]));
134        	end
135            else % array is 1D
136        	astr=arraystr(2:end-1);
137        	astr(astr==' ')='';
138        	[obj, count, errmsg, nextidx]=sscanf(astr,'%f,',[1,inf]);
139        	if(nextidx>=length(astr)-1)
140                    object=obj;
141                    pos=endpos;
142                    parse_char(']');
143                    return;
144        	end
145            end
146            if(~isempty(dim2))
147        	astr=arraystr;
148        	astr(astr=='[')='';
149        	astr(astr==']')='';
150        	astr(astr==' ')='';
151        	[obj, count, errmsg, nextidx]=sscanf(astr,'%f,',inf);
152        	if(nextidx>=length(astr)-1)
153                    object=reshape(obj,dim2,numel(obj)/dim2)';
154                    pos=endpos;
155                    parse_char(']');
156                    if(pbar>0)
157                        waitbar(pos/length(inStr),pbar,'loading ...');
158                    end
159                    return;
160        	end
161            end
162            arraystr=regexprep(arraystr,'\]\s*,','];');
163	else
164            arraystr='[';
165	end
166        try
167           if(isoct && regexp(arraystr,'"','once'))
168                error('Octave eval can produce empty cells for JSON-like input');
169           end
170           object=eval(arraystr);
171           pos=endpos;
172        catch
173         while 1
174            newopt=varargin2struct(varargin{:},'JSONLAB_ArrayDepth_',arraydepth+1);
175            val = parse_value(newopt);
176            object{end+1} = val;
177            if next_char == ']'
178                break;
179            end
180            parse_char(',');
181         end
182        end
183    end
184    if(jsonopt('SimplifyCell',0,varargin{:})==1)
185      try
186        oldobj=object;
187        object=cell2mat(object')';
188        if(iscell(oldobj) && isstruct(object) && numel(object)>1 && jsonopt('SimplifyCellArray',1,varargin{:})==0)
189            object=oldobj;
190        elseif(size(object,1)>1 && ismatrix(object))
191            object=object';
192        end
193      catch
194      end
195    end
196    parse_char(']');
197
198    if(pbar>0)
199        waitbar(pos/length(inStr),pbar,'loading ...');
200    end
201%%-------------------------------------------------------------------------
202
203function parse_char(c)
204    global pos inStr len
205    pos=skip_whitespace(pos,inStr,len);
206    if pos > len || inStr(pos) ~= c
207        error_pos(sprintf('Expected %c at position %%d', c));
208    else
209        pos = pos + 1;
210        pos=skip_whitespace(pos,inStr,len);
211    end
212
213%%-------------------------------------------------------------------------
214
215function c = next_char
216    global pos inStr len
217    pos=skip_whitespace(pos,inStr,len);
218    if pos > len
219        c = [];
220    else
221        c = inStr(pos);
222    end
223
224%%-------------------------------------------------------------------------
225
226function newpos=skip_whitespace(pos,inStr,len)
227    newpos=pos;
228    while newpos <= len && isspace(inStr(newpos))
229        newpos = newpos + 1;
230    end
231
232%%-------------------------------------------------------------------------
233function str = parseStr(varargin)
234    global pos inStr len  esc index_esc len_esc
235 % len, ns = length(inStr), keyboard
236    if inStr(pos) ~= '"'
237        error_pos('String starting with " expected at position %d');
238    else
239        pos = pos + 1;
240    end
241    str = '';
242    while pos <= len
243        while index_esc <= len_esc && esc(index_esc) < pos
244            index_esc = index_esc + 1;
245        end
246        if index_esc > len_esc
247            str = [str inStr(pos:len)];
248            pos = len + 1;
249            break;
250        else
251            str = [str inStr(pos:esc(index_esc)-1)];
252            pos = esc(index_esc);
253        end
254        nstr = length(str);
255        switch inStr(pos)
256            case '"'
257                pos = pos + 1;
258                if(~isempty(str))
259                    if(strcmp(str,'_Inf_'))
260                        str=Inf;
261                    elseif(strcmp(str,'-_Inf_'))
262                        str=-Inf;
263                    elseif(strcmp(str,'_NaN_'))
264                        str=NaN;
265                    end
266                end
267                return;
268            case '\'
269                if pos+1 > len
270                    error_pos('End of file reached right after escape character');
271                end
272                pos = pos + 1;
273                switch inStr(pos)
274                    case {'"' '\' '/'}
275                        str(nstr+1) = inStr(pos);
276                        pos = pos + 1;
277                    case {'b' 'f' 'n' 'r' 't'}
278                        str(nstr+1) = sprintf(['\' inStr(pos)]);
279                        pos = pos + 1;
280                    case 'u'
281                        if pos+4 > len
282                            error_pos('End of file reached in escaped unicode character');
283                        end
284                        str(nstr+(1:6)) = inStr(pos-1:pos+4);
285                        pos = pos + 5;
286                end
287            otherwise % should never happen
288                str(nstr+1) = inStr(pos);
289                keyboard;
290                pos = pos + 1;
291        end
292    end
293    error_pos('End of file while expecting end of inStr');
294
295%%-------------------------------------------------------------------------
296
297function num = parse_number(varargin)
298    global pos inStr isoct
299    currstr=inStr(pos:min(pos+30,end));
300    if(isoct~=0)
301        numstr=regexp(currstr,'^\s*-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?','end');
302        [num] = sscanf(currstr, '%f', 1);
303        delta=numstr+1;
304    else
305        [num, one, err, delta] = sscanf(currstr, '%f', 1);
306        if ~isempty(err)
307            error_pos('Error reading number at position %d');
308        end
309    end
310    pos = pos + delta-1;
311
312%%-------------------------------------------------------------------------
313
314function val = parse_value(varargin)
315    global pos inStr len
316
317    if(isfield(varargin{1},'progressbar_'))
318        waitbar(pos/len,varargin{1}.progressbar_,'loading ...');
319    end
320
321    switch(inStr(pos))
322        case '"'
323            val = parseStr(varargin{:});
324            return;
325        case '['
326            val = parse_array(varargin{:});
327            return;
328        case '{'
329            val = parse_object(varargin{:});
330            return;
331        case {'-','0','1','2','3','4','5','6','7','8','9'}
332            val = parse_number(varargin{:});
333            return;
334        case 't'
335            if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'true')
336                val = true;
337                pos = pos + 4;
338                return;
339            end
340        case 'f'
341            if pos+4 <= len && strcmpi(inStr(pos:pos+4), 'false')
342                val = false;
343                pos = pos + 5;
344                return;
345            end
346        case 'n'
347            if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'null')
348                val = [];
349                pos = pos + 4;
350                return;
351            end
352    end
353    error_pos('Value expected at position %d');
354%%-------------------------------------------------------------------------
355
356function error_pos(msg)
357    global pos inStr len
358    poShow = max(min([pos-15 pos-1 pos pos+20],len),1);
359    if poShow(3) == poShow(2)
360        poShow(3:4) = poShow(2)+[0 -1];  % display nothing after
361    end
362    msg = [sprintf(msg, pos) ': ' ...
363    inStr(poShow(1):poShow(2)) '<error>' inStr(poShow(3):poShow(4)) ];
364    error( ['JSONparser:invalidFormat: ' msg] );
365
366%%-------------------------------------------------------------------------
367
368function str = valid_field(str)
369global isoct
370% From MATLAB doc: field names must begin with a letter, which may be
371% followed by any combination of letters, digits, and underscores.
372% Invalid characters will be converted to underscores, and the prefix
373% "x0x[Hex code]_" will be added if the first character is not a letter.
374    pos=regexp(str,'^[^A-Za-z]','once');
375    if(~isempty(pos))
376        if(~isoct)
377            str=regexprep(str,'^([^A-Za-z])','x0x${sprintf(''%X'',unicode2native($1))}_','once');
378        else
379            str=sprintf('x0x%X_%s',char(str(1)),str(2:end));
380        end
381    end
382    if(isempty(regexp(str,'[^0-9A-Za-z_]', 'once' )))
383        return;
384    end
385    if(~isoct)
386        str=regexprep(str,'([^0-9A-Za-z_])','_0x${sprintf(''%X'',unicode2native($1))}_');
387    else
388        pos=regexp(str,'[^0-9A-Za-z_]');
389        if(isempty(pos))
390            return;
391        end
392        str0=str;
393        pos0=[0 pos(:)' length(str)];
394        str='';
395        for i=1:length(pos)
396            str=[str str0(pos0(i)+1:pos(i)-1) sprintf('_0x%X_',str0(pos(i)))];
397        end
398        if(pos(end)~=length(str))
399            str=[str str0(pos0(end-1)+1:pos0(end))];
400        end
401    end
402    %str(~isletter(str) & ~('0' <= str & str <= '9')) = '_';
403
404%%-------------------------------------------------------------------------
405function endpos = matching_quote(str,pos)
406len=length(str);
407while(pos<len)
408    if(str(pos)=='"')
409        if(~(pos>1 && str(pos-1)=='\'))
410            endpos=pos;
411            return;
412        end
413    end
414    pos=pos+1;
415end
416error('unmatched quotation mark');
417%%-------------------------------------------------------------------------
418function [endpos, e1l, e1r, maxlevel] = matching_bracket(str,pos)
419global arraytoken
420level=1;
421maxlevel=level;
422endpos=0;
423bpos=arraytoken(arraytoken>=pos);
424tokens=str(bpos);
425len=length(tokens);
426pos=1;
427e1l=[];
428e1r=[];
429while(pos<=len)
430    c=tokens(pos);
431    if(c==']')
432        level=level-1;
433        if(isempty(e1r))
434            e1r=bpos(pos);
435        end
436        if(level==0)
437            endpos=bpos(pos);
438            return
439        end
440    end
441    if(c=='[')
442        if(isempty(e1l))
443            e1l=bpos(pos);
444        end
445        level=level+1;
446        maxlevel=max(maxlevel,level);
447    end
448    if(c=='"')
449        pos=matching_quote(tokens,pos+1);
450    end
451    pos=pos+1;
452end
453if(endpos==0)
454    error('unmatched "]"');
455end
456
457function opt=varargin2struct(varargin)
458%
459% opt=varargin2struct('param1',value1,'param2',value2,...)
460%   or
461% opt=varargin2struct(...,optstruct,...)
462%
463% convert a series of input parameters into a structure
464%
465% input:
466%      'param', value: the input parameters should be pairs of a string and a value
467%       optstruct: if a parameter is a struct, the fields will be merged to the output struct
468%
469% output:
470%      opt: a struct where opt.param1=value1, opt.param2=value2 ...
471%
472
473len=length(varargin);
474opt=struct;
475if(len==0) return; end
476i=1;
477while(i<=len)
478    if(isstruct(varargin{i}))
479        opt=mergestruct(opt,varargin{i});
480    elseif(ischar(varargin{i}) && i<len)
481        opt=setfield(opt,lower(varargin{i}),varargin{i+1});
482        i=i+1;
483    else
484        error('input must be in the form of ...,''name'',value,... pairs or structs');
485    end
486    i=i+1;
487end
488function val=jsonopt(key,default,varargin)
489%
490% val=jsonopt(key,default,optstruct)
491%
492% setting options based on a struct. The struct can be produced
493% by varargin2struct from a list of 'param','value' pairs
494%
495% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu)
496%
497% $Id: loadjson.m 371 2012-06-20 12:43:06Z fangq $
498%
499% input:
500%      key: a string with which one look up a value from a struct
501%      default: if the key does not exist, return default
502%      optstruct: a struct where each sub-field is a key
503%
504% output:
505%      val: if key exists, val=optstruct.key; otherwise val=default
506%
507
508val=default;
509if(nargin<=2) return; end
510opt=varargin{1};
511if(isstruct(opt))
512    if(isfield(opt,key))
513       val=getfield(opt,key);
514    elseif(isfield(opt,lower(key)))
515       val=getfield(opt,lower(key));
516    end
517end
518
519function s=mergestruct(s1,s2)
520%
521% s=mergestruct(s1,s2)
522%
523% merge two struct objects into one
524%
525% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu)
526% date: 2012/12/22
527%
528% input:
529%      s1,s2: a struct object, s1 and s2 can not be arrays
530%
531% output:
532%      s: the merged struct object. fields in s1 and s2 will be combined in s.
533
534if(~isstruct(s1) || ~isstruct(s2))
535    error('input parameters contain non-struct');
536end
537if(length(s1)>1 || length(s2)>1)
538    error('can not merge struct arrays');
539end
540fn=fieldnames(s2);
541s=s1;
542for i=1:length(fn)
543    s=setfield(s,fn{i},getfield(s2,fn{i}));
544end
545function newdata=struct2jdata(data,varargin)
546%
547% newdata=struct2jdata(data,opt,...)
548%
549% convert a JData object (in the form of a struct array) into an array
550%
551% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu)
552%
553% input:
554%      data: a struct array. If data contains JData keywords in the first
555%            level children, these fields are parsed and regrouped into a
556%            data object (arrays, trees, graphs etc) based on JData
557%            specification. The JData keywords are
558%               "_ArrayType_", "_ArraySize_", "_ArrayData_"
559%               "_ArrayIsSparse_", "_ArrayIsComplex_"
560%      opt: (optional) a list of 'Param',value pairs for additional options
561%           The supported options include
562%               'Recursive', if set to 1, will apply the conversion to
563%                            every child; 0 to disable
564%
565% output:
566%      newdata: the covnerted data if the input data does contain a JData
567%               structure; otherwise, the same as the input.
568%
569% examples:
570%      obj=struct('_ArrayType_','double','_ArraySize_',[2 3],
571%                 '_ArrayIsSparse_',1 ,'_ArrayData_',null);
572%      ubjdata=struct2jdata(obj);
573
574fn=fieldnames(data);
575newdata=data;
576len=length(data);
577if(jsonopt('Recursive',0,varargin{:})==1)
578  for i=1:length(fn) % depth-first
579    for j=1:len
580        if(isstruct(getfield(data(j),fn{i})))
581            newdata(j)=setfield(newdata(j),fn{i},jstruct2array(getfield(data(j),fn{i})));
582        end
583    end
584  end
585end
586if(~isempty(strmatch('x0x5F_ArrayType_',fn)) && ~isempty(strmatch('x0x5F_ArrayData_',fn)))
587  newdata=cell(len,1);
588  for j=1:len
589    ndata=cast(data(j).x0x5F_ArrayData_,data(j).x0x5F_ArrayType_);
590    iscpx=0;
591    if(~isempty(strmatch('x0x5F_ArrayIsComplex_',fn)))
592        if(data(j).x0x5F_ArrayIsComplex_)
593           iscpx=1;
594        end
595    end
596    if(~isempty(strmatch('x0x5F_ArrayIsSparse_',fn)))
597        if(data(j).x0x5F_ArrayIsSparse_)
598            if(~isempty(strmatch('x0x5F_ArraySize_',fn)))
599                dim=double(data(j).x0x5F_ArraySize_);
600                if(iscpx && size(ndata,2)==4-any(dim==1))
601                    ndata(:,end-1)=complex(ndata(:,end-1),ndata(:,end));
602                end
603                if isempty(ndata)
604                    % All-zeros sparse
605                    ndata=sparse(dim(1),prod(dim(2:end)));
606                elseif dim(1)==1
607                    % Sparse row vector
608                    ndata=sparse(1,ndata(:,1),ndata(:,2),dim(1),prod(dim(2:end)));
609                elseif dim(2)==1
610                    % Sparse column vector
611                    ndata=sparse(ndata(:,1),1,ndata(:,2),dim(1),prod(dim(2:end)));
612                else
613                    % Generic sparse array.
614                    ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3),dim(1),prod(dim(2:end)));
615                end
616            else
617                if(iscpx && size(ndata,2)==4)
618                    ndata(:,3)=complex(ndata(:,3),ndata(:,4));
619                end
620                ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3));
621            end
622        end
623    elseif(~isempty(strmatch('x0x5F_ArraySize_',fn)))
624        if(iscpx && size(ndata,2)==2)
625             ndata=complex(ndata(:,1),ndata(:,2));
626        end
627        ndata=reshape(ndata(:),data(j).x0x5F_ArraySize_);
628    end
629    newdata{j}=ndata;
630  end
631  if(len==1)
632      newdata=newdata{1};
633  end
634end
635