From 72d45e2806ca715a956deb66c76fc2d9c0cb1569 Mon Sep 17 00:00:00 2001 From: fangq <fangq@786e58fb-9377-0410-9ff7-e4ac0ac0635c> Date: Sun, 16 Oct 2011 04:10:44 +0000 Subject: [PATCH] use real file instead of link git-svn-id: http://svn.code.sf.net/p/iso2mesh/code/trunk/jsonlab@313 786e58fb-9377-0410-9ff7-e4ac0ac0635c --- loadjson.m | 402 +++++++++++++++++++++++++++++++++++++++++++++++++++++ savejson.m | 277 ++++++++++++++++++++++++++++++++++++ 2 files changed, 679 insertions(+) create mode 100644 loadjson.m create mode 100644 savejson.m diff --git a/loadjson.m b/loadjson.m new file mode 100644 index 0000000..7a6bd96 --- /dev/null +++ b/loadjson.m @@ -0,0 +1,402 @@ +function data = loadjson(fname) +% +% data=loadjson(fname) +% +% parse a JSON (JavaScript Object Notation) file or string +% +% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) +% date: 2011/09/09 +% Nedialko Krouchev: http://www.mathworks.com/matlabcentral/fileexchange/25713 +% date: 2009/11/02 +% François Glineur: http://www.mathworks.com/matlabcentral/fileexchange/23393 +% date: 2009/03/22 +% Joel Feenstra: http://www.mathworks.com/matlabcentral/fileexchange/20565 +% date: 2008/07/03 +% +% input: +% fname: input file name, if fname contains "{}" or "[]", fname +% will be interpreted as a JSON string +% +% output: +% dat: a cell array, where {...} blocks are converted into cell arrays, +% and [...] are converted to arrays +% +% license: +% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details +% +% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) +% + +global pos inStr len esc index_esc len_esc isoct arraytoken + +if(regexp(fname,'[\{\}\]\[]','once')) + string=fname; +elseif(exist(fname,'file')) + fid = fopen(fname,'rt'); + string = fscanf(fid,'%c'); + fclose(fid); +else + error('input file does not exist'); +end + +pos = 1; len = length(string); inStr = string; +isoct=exist('OCTAVE_VERSION'); +arraytoken=find(inStr=='[' | inStr==']' | inStr=='"'); + +% String delimiters and escape chars identified to improve speed: +esc = find(inStr=='"' | inStr=='\' ); % comparable to: regexp(inStr, '["\\]'); +index_esc = 1; len_esc = length(esc); + +if pos <= len + switch(next_char) + case '{' + data = parse_object; + case '[' + data = parse_array; + otherwise + error_pos('Outer level structure must be an object or an array'); + end +end % if + +len=length(data); +if(len==1 && isstruct(data)) + data=jstruct2array(data); +else + for i=1:len + if(isstruct(data(i))) + data(i)=jstruct2array(data(i)); + end + end +end + +%%------------------------------------------------------------------------- +function newdata=jstruct2array(data) +fn=fieldnames(data); +newdata=data; +for i=1:length(fn) % depth-first + if(isstruct(getfield(data,fn{i}))) + newdata=setfield(newdata,fn{i},jstruct2array(getfield(data,fn{i}))); + end +end +if(~isempty(strmatch('x_ArrayType_',fn)) && ~isempty(strmatch('x_ArrayData_',fn))) + newdata=cast(data.x_ArrayData_,data.x_ArrayType_); + iscpx=0; + if(~isempty(strmatch('x_ArrayIsComplex_',fn))) + if(data.x_ArrayIsComplex_) + iscpx=1; + end + end + if(~isempty(strmatch('x_ArrayIsSparse_',fn))) + if(data.x_ArrayIsSparse_) + if(iscpx && size(newdata,2)==4) + newdata(:,3)=complex(newdata(:,3),newdata(:,4)); + end + if(~isempty(strmatch('x_ArraySize_',fn))) + dim=data.x_ArraySize_; + newdata=sparse(newdata(:,1),newdata(:,2),newdata(:,3),dim(1),prod(dim(2:end))); + else + newdata=sparse(newdata(:,1),newdata(:,2),newdata(:,3)); + end + end + elseif(~isempty(strmatch('x_ArraySize_',fn))) + if(iscpx && size(newdata,2)==2) + newdata=complex(newdata(:,1),newdata(:,2)); + end + newdata=reshape(newdata(:),data.x_ArraySize_); + end +end + +%%------------------------------------------------------------------------- +function object = parse_object + parse_char('{'); + object = []; + if next_char ~= '}' + while 1 + str = parseStr; + if isempty(str) + error_pos('Name of value at position %d cannot be empty'); + end + parse_char(':'); + val = parse_value; + eval( sprintf( 'object.%s = val;', valid_field(str) ) ); + if next_char == '}' + break; + end + parse_char(','); + end + end + parse_char('}'); + +%%------------------------------------------------------------------------- + +function object = parse_array % JSON array is written in row-major order +global pos inStr + parse_char('['); + object = cell(0, 1); + if next_char ~= ']' + endpos=matching_bracket(inStr,pos); + arraystr=['[' inStr(pos:endpos)]; + arraystr(find(arraystr==sprintf('\n')))=[]; + arraystr(find(arraystr==sprintf('\r')))=[]; + arraystr=regexprep(arraystr,'\]\s*,','];'); + arraystr=regexprep(arraystr,'"_NaN_"','NaN'); + arraystr=regexprep(arraystr,'"([-+]*)_Inf_"','$1Inf'); + try + if(isoct && regexp(arraystr,'"','once')) + error('Octave eval can produce empty cells for JSON-like input'); + end + object=eval(arraystr); + pos=endpos; + catch + while 1 + val = parse_value; + object{end+1} = val; + if next_char == ']' + break; + end + parse_char(','); + end + end + end + try + object=cell2mat(object')'; + if(size(object,1)>1 && ndims(object)==2) + object=object'; + end + catch + end + parse_char(']'); + +%%------------------------------------------------------------------------- + +function parse_char(c) + global pos inStr len + skip_whitespace; + if pos > len || inStr(pos) ~= c + error_pos(sprintf('Expected %c at position %%d', c)); + else + pos = pos + 1; + skip_whitespace; + end + +%%------------------------------------------------------------------------- + +function c = next_char + global pos inStr len + skip_whitespace; + if pos > len + c = []; + else + c = inStr(pos); + end + +%%------------------------------------------------------------------------- + +function skip_whitespace + global pos inStr len + while pos <= len && isspace(inStr(pos)) + pos = pos + 1; + end + +%%------------------------------------------------------------------------- +function str = parseStr + global pos inStr len esc index_esc len_esc + % len, ns = length(inStr), keyboard + if inStr(pos) ~= '"' + error_pos('String starting with " expected at position %d'); + else + pos = pos + 1; + end + str = ''; + while pos <= len + while index_esc <= len_esc && esc(index_esc) < pos + index_esc = index_esc + 1; + end + if index_esc > len_esc + str = [str inStr(pos:len)]; + pos = len + 1; + break; + else + str = [str inStr(pos:esc(index_esc)-1)]; + pos = esc(index_esc); + end + nstr = length(str); switch inStr(pos) + case '"' + pos = pos + 1; + if(~isempty(str)) + if(strcmp(str,'_Inf_')) + str=Inf; + elseif(strcmp(str,'-_Inf_')) + str=-Inf; + elseif(strcmp(str,'_NaN_')) + str=NaN; + end + end + return; + case '\' + if pos+1 > len + error_pos('End of file reached right after escape character'); + end + pos = pos + 1; + switch inStr(pos) + case {'"' '\' '/'} + str(nstr+1) = inStr(pos); + pos = pos + 1; + case {'b' 'f' 'n' 'r' 't'} + str(nstr+1) = sprintf(['\' inStr(pos)]); + pos = pos + 1; + case 'u' + if pos+4 > len + error_pos('End of file reached in escaped unicode character'); + end + str(nstr+(1:6)) = inStr(pos-1:pos+4); + pos = pos + 5; + end + otherwise % should never happen + str(nstr+1) = inStr(pos), keyboard + pos = pos + 1; + end + end + error_pos('End of file while expecting end of inStr'); + +%%------------------------------------------------------------------------- + +function num = parse_number + global pos inStr len isoct + currstr=inStr(pos:end); + numstr=0; + if(isoct~=0) + numstr=regexp(currstr,'^\s*-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?','end'); + [num, one] = sscanf(currstr, '%f', 1); + delta=numstr+1; + else + [num, one, err, delta] = sscanf(currstr, '%f', 1); + if ~isempty(err) + error_pos('Error reading number at position %d'); + end + end + pos = pos + delta-1; + +%%------------------------------------------------------------------------- + +function val = parse_value + global pos inStr len + true = 1; false = 0; + + switch(inStr(pos)) + case '"' + val = parseStr; + return; + case '[' + val = parse_array; + return; + case '{' + val = parse_object; + return; + case {'-','0','1','2','3','4','5','6','7','8','9'} + val = parse_number; + return; + case 't' + if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'true') + val = true; + pos = pos + 4; + return; + end + case 'f' + if pos+4 <= len && strcmpi(inStr(pos:pos+4), 'false') + val = false; + pos = pos + 5; + return; + end + case 'n' + if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'null') + val = []; + pos = pos + 4; + return; + end + end + error_pos('Value expected at position %d'); +%%------------------------------------------------------------------------- + +function error_pos(msg) + global pos inStr len + poShow = max(min([pos-15 pos-1 pos pos+20],len),1); + if poShow(3) == poShow(2) + poShow(3:4) = poShow(2)+[0 -1]; % display nothing after + end + msg = [sprintf(msg, pos) ': ' ... + inStr(poShow(1):poShow(2)) '<error>' inStr(poShow(3):poShow(4)) ]; + error( ['JSONparser:invalidFormat: ' msg] ); + +%%------------------------------------------------------------------------- + +function str = valid_field(str) +global isoct +% From MATLAB doc: field names must begin with a letter, which may be +% followed by any combination of letters, digits, and underscores. +% Invalid characters will be converted to underscores, and the prefix +% "x_" will be added if first character is not a letter. + if ~isletter(str(1)) || str(1)>'z' + str = ['x' str]; + end + if(isempty(regexp(str,'[^0-9A-Za-z_]', 'once' ))) return; end + if(~isoct) + str=regexprep(str,'([^0-9A-Za-z_])','_0x${sprintf(''%X'',unicode2native($1))}_'); + else + pos=regexp(str,'[^0-9A-Za-z_]'); + if(isempty(pos)) return; end + str0=str; + pos0=[0 pos(:)' length(str)]; + str=''; + for i=1:length(pos) + str=[str str0(pos0(i)+1:pos(i)-1) sprintf('_0x%X_',str0(pos(i)))]; + end + if(pos(end)~=length(str)) + str=[str str0(pos0(end-1)+1:pos0(end))]; + end + end + %str(~isletter(str) & ~('0' <= str & str <= '9')) = '_'; + +%%------------------------------------------------------------------------- +function endpos = matching_quote(str,pos) +len=length(str); +while(pos<len) + if(str(pos)=='"') + if(~(pos>1 && str(pos-1)=='\')) + endpos=pos; + return; + end + end + pos=pos+1; +end + +%%------------------------------------------------------------------------- +function endpos = matching_bracket(str,pos) +global arraytoken +level=1; +endpos=0; +bpos=arraytoken(arraytoken>=pos); +tokens=str(bpos); +len=length(tokens); +pos=1; +while(pos<=len) + c=tokens(pos); + if(c==']') + level=level-1; + if(level==0) + endpos=bpos(pos); + return + end + end + if(c=='[') + level=level+1; + end + if(c=='"') + pos=matching_quote(tokens,pos+1); + end + pos=pos+1; +end +if(endpos==0) + error('unmatched "]"'); +end + diff --git a/savejson.m b/savejson.m new file mode 100644 index 0000000..d967286 --- /dev/null +++ b/savejson.m @@ -0,0 +1,277 @@ +function json=savejson(rootname,obj,varargin) +% +% json=savejson(rootname,obj,opt) +% +% convert a MATLAB object (cell, struct or array) into a JSON (JavaScript +% Object Notation) string +% +% authors:Qianqian Fang (fangq<at> nmr.mgh.harvard.edu) +% date: 2011/09/09 +% +% input: +% rootname: name of the root-object, if set to '', will use variable name +% obj: a MATLAB object (array, cell, cell array, struct, struct array) +% opt: a struct for additional options, use [] if all use default +% opt can have the following fields (first in [.|.] is the default) +% opt.FloatFormat ['%.10g'|string]: format to show each numeric element +% of a 1D/2D array; +% opt.ArrayIndent [1|0]: if 1, output explicit data array with +% precedent indentation; if 0, no indentation +% opt.ArrayToStruct[0|1]: when set to 0, savejson outputs 1D/2D +% array in JSON array format; if sets to 1, an +% array will be shown as a struct with fields +% "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for +% sparse arrays, the non-zero elements will be +% saved to _ArrayData_ field in triplet-format i.e. +% (ix,iy,val) and "_ArrayIsSparse_" will be added +% with a value of 1; for a complex array, the +% _ArrayData_ array will include two columns +% (4 for sparse) to record the real and imaginary +% parts, and also "_ArrayIsComplex_":1 is added. +% opt.ParseLogical [0|1]: if this is set to 1, logical array elem +% will use true/false rather than 1/0. +% +% output: +% json: a string in the JSON format (see http://json.org) +% +% examples: +% a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... +% 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); +% savejson('mesh',a) +% savejson('',a,struct('ArrayIndent',0,'FloatFormat','\t%.5g')) +% +% license: +% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details +% +% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) +% + +varname=inputname(2); +if(~isempty(rootname)) + varname=rootname; +end +rootisarray=0; +rootlevel=1; +if((isnumeric(obj) || islogical(obj) || ischar(obj)) && isempty(rootname)) + rootisarray=1; + rootlevel=0; + varname=''; +end +json=obj2json(varname,obj,rootlevel,varargin{:}); +if(rootisarray) + json=sprintf('%s\n',json); +else + json=sprintf('{\n%s\n}\n',json); +end + +%%------------------------------------------------------------------------- +function txt=obj2json(name,item,level,varargin) + +cname=class(item); + +if(iscell(item)) + txt=cell2json(name,item,level,varargin{:}); +elseif(isstruct(item)) + txt=struct2json(name,item,level,varargin{:}); +elseif(ischar(item)) + txt=str2json(name,item,level,varargin{:}); +else + txt=mat2json(name,item,level,varargin{:}); +end + +%%------------------------------------------------------------------------- +function txt=cell2json(name,item,level,varargin) +txt=''; +if(~iscell(item)) + error('input is not a cell'); +end + +dim=size(item); +len=numel(item); % let's handle 1D cell first +padding1=repmat(sprintf('\t'),1,level-1); +padding0=repmat(sprintf('\t'),1,level); +if(len>1) txt=sprintf('%s"%s": [\n',padding0, name); name=''; end +for i=1:len + txt=sprintf('%s%s%s',txt,padding1,obj2json(name,item{i},level+(len>1),varargin{:})); + if(i<len) txt=sprintf('%s%s',txt,sprintf(',\n')); end +end +if(len>1) txt=sprintf('%s\n%s]',txt,padding0); end + +%%------------------------------------------------------------------------- +function txt=struct2json(name,item,level,varargin) +txt=''; +if(~isstruct(item)) + error('input is not a struct'); +end +len=numel(item); +padding1=repmat(sprintf('\t'),1,level); +padding0=repmat(sprintf('\t'),1,level+1); +sep=','; + +if(~isempty(name)) + if(len>1) txt=sprintf('%s"%s": [\n',padding1,name); end +else + if(len>1) txt=sprintf('%s[\n',padding1); end +end +for e=1:len + names = fieldnames(item(e)); + if(~isempty(name) && len==1) + txt=sprintf('%s%s"%s": {\n',txt,padding1, name); + else + txt=sprintf('%s%s{\n',txt,repmat(sprintf('\t'),1,level+(len>1))); + end + if(~isempty(names)) + for i=1:length(names) + txt=sprintf('%s%s',txt,obj2json(names{i},getfield(item(e),... + names{i}),level+1+(len>1),varargin{:})); + if(i<length(names)) txt=sprintf('%s%s',txt,','); end + txt=sprintf('%s%s',txt,sprintf('\n')); + end + end + txt=sprintf('%s%s}',txt,repmat(sprintf('\t'),1,level+(len>1))); + if(e==len) sep=''; end + if(e<len) txt=sprintf('%s%s',txt,sprintf(',\n')); end +end +if(len>1) txt=sprintf('%s\n%s]',txt,padding1); end + +%%------------------------------------------------------------------------- +function txt=str2json(name,item,level,varargin) +global isoct +txt=''; +if(~ischar(item)) + error('input is not a string'); +end +len=size(item,1); +sep=sprintf(',\n'); + +padding1=repmat(sprintf('\t'),1,level); +padding0=repmat(sprintf('\t'),1,level+1); + +if(~isempty(name)) + if(len>1) txt=sprintf('%s"%s": [\n',padding1,name); end +else + if(len>1) txt=sprintf('%s[\n',padding1); end +end +for e=1:len + if(isoct) + val=regexprep(item(e,:),'\\','\\'); + val=regexprep(val,'"','\"'); + val=regexprep(val,'^"','\"'); + else + val=regexprep(item(e,:),'\\','\\\\'); + val=regexprep(val,'"','\\"'); + val=regexprep(val,'^"','\\"'); + end + if(len==1) + obj=['"' name '": ' '"',val,'"']; + if(isempty(name)) obj=['"',val,'"']; end + txt=sprintf('%s%s%s%s',txt,repmat(sprintf('\t'),1,level),obj); + else + txt=sprintf('%s%s%s%s',txt,repmat(sprintf('\t'),1,level+1),['"',val,'"']); + end + if(e==len) sep=''; end + txt=sprintf('%s%s',txt,sep); +end +if(len>1) txt=sprintf('%s\n%s%s',txt,padding1,']'); end + +%%------------------------------------------------------------------------- +function txt=mat2json(name,item,level,varargin) +if(~isnumeric(item) && ~islogical(item)) + error('input is not an array'); +end + +padding1=repmat(sprintf('\t'),1,level); +padding0=repmat(sprintf('\t'),1,level+1); + +if(length(size(item))>2 || issparse(item) || ~isreal(item) || jsonopt('ArrayToStruct',0,varargin{:})) + if(isempty(name)) + txt=sprintf('%s{\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... + padding1,padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); + else + txt=sprintf('%s"%s": {\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... + padding1,name,padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); + end +else + if(isempty(name)) + txt=sprintf('%s%s',padding1,matdata2json(item,level+1,varargin{:})); + else + txt=sprintf('%s"%s": %s',padding1,name,matdata2json(item,level+1,varargin{:})); + end + return; +end +dataformat='%s%s%s%s%s'; + +if(issparse(item)) + [ix,iy]=find(item); + data=full(item(find(item))); + if(~isreal(item)) + data=[real(data(:)),imag(data(:))]; + txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); + end + txt=sprintf(dataformat,txt,padding0,'"_ArrayIsSparse_": ','1', sprintf(',\n')); + if(find(size(item)==1)) + txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... + matdata2json([ix,data],level+2,varargin{:}), sprintf('\n')); + else + txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... + matdata2json([ix,iy,data],level+2,varargin{:}), sprintf('\n')); + end +else + if(isreal(item)) + txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... + matdata2json(item(:)',level+2,varargin{:}), sprintf('\n')); + else + txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); + txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... + matdata2json([real(item(:)) imag(item(:))],level+2,varargin{:}), sprintf('\n')); + end +end +txt=sprintf('%s%s%s',txt,padding1,'}'); + +%%------------------------------------------------------------------------- +function txt=matdata2json(mat,level,varargin) +if(size(mat,1)==1) + pre=''; + post=''; + level=level-1; +else + pre=sprintf('[\n'); + post=sprintf('\n%s]',repmat(sprintf('\t'),1,level-1)); +end +if(isempty(mat)) + txt='null'; + return; +end +floatformat=jsonopt('FloatFormat','%.10g',varargin{:}); +formatstr=['[' repmat([floatformat ','],1,size(mat,2)-1) [floatformat sprintf('],\n')]]; + +if(nargin>=2 && size(mat,1)>1 && jsonopt('ArrayIndent',1,varargin{:})==1) + formatstr=[repmat(sprintf('\t'),1,level) formatstr]; +end +txt=sprintf(formatstr,mat'); +txt(end-1:end)=[]; +if(islogical(mat) && jsonopt('ParseLogical',0,varargin{:})==1) + txt=regexprep(txt,'1','true'); + txt=regexprep(txt,'0','false'); +end +%txt=regexprep(mat2str(mat),'\s+',','); +%txt=regexprep(txt,';',sprintf('],\n[')); +% if(nargin>=2 && size(mat,1)>1) +% txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']); +% end +txt=[pre txt post]; +if(any(isinf(mat(:)))) + txt=regexprep(txt,'([-+]*)Inf','"$1_Inf_"'); +end +if(any(isnan(mat(:)))) + txt=regexprep(txt,'NaN','"_NaN_"'); +end + +%%------------------------------------------------------------------------- +function val=jsonopt(key,default,varargin) +val=default; +if(nargin<=2) return; end +opt=varargin{1}; +if(isstruct(opt) && isfield(opt,key)) + val=getfield(opt,key); +end -- GitLab