├── private ├── README_JSONLAB.txt ├── http_createHeader.m ├── mergestruct.m ├── jsonopt.m ├── varargin2struct.m ├── http_paramsToString.m ├── urlread_notes.txt ├── urlread2.m ├── loadubjson.m ├── saveubjson.m ├── savejson.m └── loadjson.m ├── TODO.md ├── README.md ├── MakeSlackAttachment.m └── SendSlackNotification.m /private/README_JSONLAB.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DylanMuir/SlackMatlab/HEAD/private/README_JSONLAB.txt -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # List of to-do items # 2 | 3 | - [ ] Add support for ```multipart/form``` uploads via ```urlread2```. 4 | - [ ] Add support for token-based authentication. Caching of auth token? 5 | - [ ] Add support for posting files via [```files.upload```](https://api.slack.com/methods/files.upload) API. 6 | - [ ] Add support for OAuth2 authentication. 7 | -------------------------------------------------------------------------------- /private/http_createHeader.m: -------------------------------------------------------------------------------- 1 | function header = http_createHeader(name,value) 2 | %http_createHeader Simple function for creating input header to urlread2 3 | % 4 | % header = http_createHeader(name,value) 5 | % 6 | % CODE: header = struct('name',name,'value',value); 7 | % 8 | % See Also: 9 | % urlread2 10 | 11 | header = struct('name',name,'value',value); -------------------------------------------------------------------------------- /private/mergestruct.m: -------------------------------------------------------------------------------- 1 | function s=mergestruct(s1,s2) 2 | % 3 | % s=mergestruct(s1,s2) 4 | % 5 | % merge two struct objects into one 6 | % 7 | % authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) 8 | % date: 2012/12/22 9 | % 10 | % input: 11 | % s1,s2: a struct object, s1 and s2 can not be arrays 12 | % 13 | % output: 14 | % s: the merged struct object. fields in s1 and s2 will be combined in s. 15 | % 16 | % license: 17 | % Simplified BSD License 18 | % 19 | % -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) 20 | % 21 | 22 | if(~isstruct(s1) || ~isstruct(s2)) 23 | error('input parameters contain non-struct'); 24 | end 25 | if(length(s1)>1 || length(s2)>1) 26 | error('can not merge struct arrays'); 27 | end 28 | fn=fieldnames(s2); 29 | s=s1; 30 | for i=1:length(fn) 31 | s=setfield(s,fn{i},getfield(s2,fn{i})); 32 | end 33 | 34 | -------------------------------------------------------------------------------- /private/jsonopt.m: -------------------------------------------------------------------------------- 1 | function val=jsonopt(key,default,varargin) 2 | % 3 | % val=jsonopt(key,default,optstruct) 4 | % 5 | % setting options based on a struct. The struct can be produced 6 | % by varargin2struct from a list of 'param','value' pairs 7 | % 8 | % authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) 9 | % 10 | % $Id: loadjson.m 371 2012-06-20 12:43:06Z fangq $ 11 | % 12 | % input: 13 | % key: a string with which one look up a value from a struct 14 | % default: if the key does not exist, return default 15 | % optstruct: a struct where each sub-field is a key 16 | % 17 | % output: 18 | % val: if key exists, val=optstruct.key; otherwise val=default 19 | % 20 | % license: 21 | % Simplified BSD License 22 | % 23 | % -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) 24 | % 25 | 26 | val=default; 27 | if(nargin<=2) return; end 28 | opt=varargin{1}; 29 | if(isstruct(opt) && isfield(opt,key)) 30 | val=getfield(opt,key); 31 | end 32 | 33 | -------------------------------------------------------------------------------- /private/varargin2struct.m: -------------------------------------------------------------------------------- 1 | function opt=varargin2struct(varargin) 2 | % 3 | % opt=varargin2struct('param1',value1,'param2',value2,...) 4 | % or 5 | % opt=varargin2struct(...,optstruct,...) 6 | % 7 | % convert a series of input parameters into a structure 8 | % 9 | % authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) 10 | % date: 2012/12/22 11 | % 12 | % input: 13 | % 'param', value: the input parameters should be pairs of a string and a value 14 | % optstruct: if a parameter is a struct, the fields will be merged to the output struct 15 | % 16 | % output: 17 | % opt: a struct where opt.param1=value1, opt.param2=value2 ... 18 | % 19 | % license: 20 | % Simplified BSD License 21 | % 22 | % -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) 23 | % 24 | 25 | len=length(varargin); 26 | opt=struct; 27 | if(len==0) return; end 28 | i=1; 29 | while(i<=len) 30 | if(isstruct(varargin{i})) 31 | opt=mergestruct(opt,varargin{i}); 32 | elseif(ischar(varargin{i}) && i cmd=search&db=pubmed&term=wtf+batman 26 | % 27 | % IMPORTANT: This function does not filter parameters, sort them, 28 | % or remove empty inputs (if necessary), this must be done before hand 29 | 30 | if ~exist('encodeOption','var') 31 | encodeOption = 1; 32 | end 33 | 34 | if size(params,2) == 2 && size(params,1) > 1 35 | params = params'; 36 | params = params(:); 37 | end 38 | 39 | str = ''; 40 | for i=1:2:length(params) 41 | if (i == 1), separator = ''; else separator = '&'; end 42 | switch encodeOption 43 | case 1 44 | param = urlencode(params{i}); 45 | value = urlencode(params{i+1}); 46 | % case 2 47 | % param = oauth.percentEncodeString(params{i}); 48 | % value = oauth.percentEncodeString(params{i+1}); 49 | % header = http_getContentTypeHeader(1); 50 | otherwise 51 | error('Case not used') 52 | end 53 | str = [str separator param '=' value]; %#ok 54 | end 55 | 56 | switch encodeOption 57 | case 1 58 | header = http_createHeader('Content-Type','application/x-www-form-urlencoded'); 59 | end 60 | 61 | 62 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README # 2 | 3 | This repository contains ```Matlab``` functions to send notifications to a Slack channel or user, via the Slack [Incoming Webhooks](https://slack.com/services/new/incoming-webhook) API. 4 | 5 | ## Usage ## 6 | 7 | ```SendSlackNotification``` is used to send a notification to a URL provided by Slack, for a configured [Incoming Webhooks](https://slack.com/services/new/incoming-webhook) integration. See the documentation for this function for information on options. 8 | 9 | ```MakeSlackAttachments``` can be used to generate Slack [message attachments](https://api.slack.com/docs/attachments), which can then be sent as notifications using ```SendSlackNotification```. See the documentation for this function for more information. 10 | 11 | ## Set up ## 12 | 13 | 1. Configure an [Incoming Webhooks](https://slack.com/services/new/incoming-webhook) integration for your team, from your team's [services](https://slack.com/services) page. 14 | 2. Copy the Webhook URL once the service is configured, and store it in a ```Matlab``` string. 15 | 3. Clone the [SlackMatlab repository](https://github.com/DylanMuir/SlackMatlab), and add the root directory to the ```Matlab``` path using ```pathtool```. 16 | 4. Call ```SendSlackNotification```, passing the Webhook URL as an argument. 17 | 18 | ### Example ### 19 | 20 | ```matlab 21 | % - Create a message attachment to send with a notification 22 | % (optional; several message attachments can be sent with a single notification) 23 | sA = MakeSlackAttachment('New open task [urgent]: ', 'Text of the notification message', ... 24 | 'Text that will be displayed before the message', '#0000ff', ... 25 | {'Field 1 title', 'This is a field that will be shown in a table'}, ... 26 | {'Field 2 title', 'This is another field that will be shown in a table'}); 27 | 28 | % - Send the notification, with the attached message 29 | SendSlackNotification('https://hooks.slack.com/services/this/is/your/webhook/url', ... 30 | 'I sent this notification from matlab, on behalf of @username.', '#target-channel', ... 31 | 'Name to post under', 'http://www.icon.com/url/to/icon/image.png', [], sA); 32 | ``` 33 | 34 | ## Emojis ## 35 | 36 | A list of Emojis supported by Slack is available from http://www.emoji-cheat-sheet.com. 37 | 38 | 39 | ## Acknowledgements ## 40 | 41 | Contains code from [URLREAD2](http://www.mathworks.com/matlabcentral/fileexchange/35693-urlread2) and [JSONLAB](http://www.mathworks.com/matlabcentral/fileexchange/33381-jsonlab--a-toolbox-to-encode-decode-json-files-in-matlab-octave). 42 | -------------------------------------------------------------------------------- /MakeSlackAttachment.m: -------------------------------------------------------------------------------- 1 | function sAttachment = MakeSlackAttachment(strFallback, strText, strPreText, strColor, varargin) 2 | 3 | % MakeSlackAttachment - FUNCTION Construct an attachment to add to a Slack notification 4 | % 5 | % Usage: sAttachment = MakeSlackAttachment(strFallback, , , strColor, ...) 6 | % Usage: sAttachment = MakeSlackAttachment(..., {strField1Title, strField1Value, }, {strField2Title, strField2Value, }, ...) 7 | % 8 | % Creates an attachment structure, to send along with a Slack notification 9 | % using 'SendSlackNotification'. See 10 | % and for information. 11 | % 12 | % 'strFallback' is a text string, which is displayed when the notification 13 | % cannot be displayed in full. 14 | % 15 | % 'strText' (optional) is a text string containing the text of the notification. 16 | % 17 | % 'strPreText' (optional) is a text string that will be displayed before 18 | % the notification text. 19 | % 20 | % 'strColor' (optional) is a hex color string (e.g. '#ff3300'), or one of 21 | % 'good', 'warning', 'danger'. 22 | % 23 | % These parameters can be followed by a list of cell arrays, each array 24 | % containing a "field" to be added to the attachment. Each field array must 25 | % be in the format {strTitle, strValue <, bShort>}. 'strTitle' is a text 26 | % string containing the title of the field. 'strValue' is a text string 27 | % containing the value to be shown for that field. 28 | % 29 | % If present, these fields will be shown below the attachment in a 30 | % notificaition. 31 | 32 | % Author: Dylan Muir 33 | % Created: 19th September, 2014 34 | 35 | % -- Check arguments 36 | 37 | if (nargin < 1) 38 | help MakeSlackAttachment; 39 | error('*** MakeSlackAttachment: Incorrect usage'); 40 | end 41 | 42 | % - Include fallback text 43 | sAttachment.fallback = strFallback; 44 | 45 | % - Include extended text 46 | if (exist('strText', 'var') && ~isempty(strText)) 47 | sAttachment.text = strText; 48 | end 49 | 50 | % - Include pre-text 51 | if (exist('strPreText', 'var') && ~isempty(strPreText)) 52 | sAttachment.pretext = strPreText; 53 | end 54 | 55 | % - Include extended text 56 | if (exist('strColor', 'var') && ~isempty(strColor)) 57 | sAttachment.color = strColor; 58 | end 59 | 60 | % - Include list of fields 61 | for (nFieldIndex = numel(varargin):-1:1) 62 | sThisField = []; 63 | sThisField.title = varargin{nFieldIndex}{1}; 64 | sThisField.value = varargin{nFieldIndex}{2}; 65 | 66 | % - Add "short" 67 | if ((numel(varargin{nFieldIndex}) > 2) && varargin{nFieldIndex}{3}) 68 | sThisField.short = 'true'; 69 | else 70 | sThisField.short = 'false'; 71 | end 72 | 73 | sAttachment.fields{nFieldIndex} = sThisField; 74 | end 75 | 76 | % - Add a dummy field if necessary (to ensure proper JSON formatting) 77 | if (numel(varargin) == 1) 78 | sThisField = []; 79 | sThisField.title = ''; 80 | sAttachment.fields{end+1} = sThisField; 81 | end 82 | 83 | 84 | % --- END of MakeSlackAttachment.m --- 85 | -------------------------------------------------------------------------------- /private/urlread_notes.txt: -------------------------------------------------------------------------------- 1 | ========================================================================== 2 | Unicode & Matlab 3 | ========================================================================== 4 | native2unicode - works with uint8, fails with char 5 | 6 | Taking a unicode character and encoding as bytes: 7 | unicode2native(char(1002),'UTF-8') 8 | back to the character: 9 | native2unicode(uint8([207 170]),'UTF-8') 10 | this doesn't work: 11 | native2unicode(char([207 170]),'UTF-8') 12 | in documentation: If BYTES is a CHAR vector, it is returned unchanged. 13 | 14 | Java - only supports int8 15 | Matlab to Java -> uint8 or int8 to bytes 16 | Java to Matlab -> bytes to int8 17 | char - 16 bit 18 | 19 | Maintenance of underlying bytes: 20 | typecast(java.lang.String(uint8(250)).getBytes,'uint8') = 250 21 | see documentation: Handling Data Returned from a Java Method 22 | 23 | Command Window difficulty 24 | -------------------------------------------------------------------- 25 | The typical font in the Matlab command window will often fail to render 26 | unicode properly. I often can see unicode better in the variable editor 27 | although this may be fixed if you change your font preferences ... 28 | Copying unicode from the command window often results in the 29 | generations of the value 26 (aka substitute) 30 | 31 | More documentation on input/output to urlread2 to follow eventually ... 32 | 33 | small notes to self: 34 | for output 35 | native2unicode(uint8(output),encoding) 36 | 37 | ========================================================================== 38 | HTTP Headers 39 | ========================================================================== 40 | Handling of repeated http readers is a bit of a tricky situation. Most 41 | headers are not repeated although sometimes http clients will assume this 42 | for too many headers which can result in a problem if you want to see 43 | duplicated headers. I've passed the problem onto the user who can decide 44 | to handle it how they wish instead of providing the right solution, which 45 | after some brief searching, I am not sure exists. 46 | 47 | ========================================================================== 48 | PROBLEMS 49 | ========================================================================== 50 | 1) Page requires following a redirect: 51 | %------------------------------------------- 52 | ref: http://www.mathworks.com/matlabcentral/newsreader/view_thread/302571 53 | fix: FOLLOW_REDIRECTS is enabled by default, you're fine. 54 | 55 | 2) Basic authentication required: 56 | %------------------------------------------ 57 | Create and pass in the following header: 58 | user = 'test'; 59 | password = 'test'; 60 | encoder = sun.misc.BASE64Encoder(); 61 | str = java.lang.String([user ':' password]) %NOTE: format may be 62 | %different for your server 63 | header = http_createHeader('Authorization',char(encoder.encode(str.getBytes()))) 64 | NOTE: Ideally you would make this a function 65 | 66 | 3) The text returned doesn't make sense. 67 | %----------------------------------------- 68 | The text may not be encoded correctly. Requires native2unicode function. 69 | See Unicode & Matlab section above. 70 | 71 | 4) I get a different result in my web browser than I do in Matlab 72 | %----------------------------------------- 73 | This is generally seen for two reasons. 74 | 1 - The easiest and silly reason is user agent filtering. 75 | When you make a request you identify yourself 76 | as being a particular "broswer" or "user agent". Setting a header 77 | with the user agent of the browser may fix the problem. 78 | See: http://en.wikipedia.org/wiki/User_agent 79 | See: http://whatsmyuseragent.com 80 | value = '' 81 | header = http_createHeader('User-Agent',value); 82 | 2 - You are not processing cookies and the server is not sending 83 | you information because you haven't sent it cookies (everyone likes em!) 84 | I've implemented cookie support but it requires some extra files that 85 | I need to clean up. Feel free to email me if you'd really like to have them. 86 | 87 | -------------------------------------------------------------------------------- /SendSlackNotification.m: -------------------------------------------------------------------------------- 1 | function [strHTTPOutput, sHTTPExtra] = SendSlackNotification(strHookURL, strText, strTarget, strUsername, strIconURL, strIconEmoji, csAttachments) 2 | 3 | % SendSlackNotification - FUNCTION Send a customisable notification via a Slack webhook integration 4 | % 5 | % Usage: [strHTTPOutput, sHTTPExtra] = SendSlackNotification(strHookURL, strText, , ...) 6 | % SendSlackNotification(..., sAttachment) 7 | % SendSlackNotification(..., {sAttachment1 sAttachment2}) 8 | % 9 | % Use the Slack webhooks API to send a notification to a Slack channel or 10 | % user. See See , 11 | % and 12 | % for further information. 13 | % 14 | % 'strHookURL' is the full URL configured for webhook integration from 15 | % Slack. 16 | % 17 | % 'strText' is a string containing (possibly marked-up) text, which will be 18 | % sent as the notification. 19 | % 20 | % Optional arguments: 21 | % strTarget: A channel name ('#channel') or a user name ('@username') to 22 | % send the notification to. By default, the channel 23 | % configured within Slack for the provided webhook URL will 24 | % be used. 25 | % strUsername: A text string defining the name under which the 26 | % notification will be posted. By default, Slack uses 27 | % 'incoming-webhook'. 28 | % strIconURL: A URL referencing an image file to use as the icon for the 29 | % notification. By default, Slack uses a webhook icon. 30 | % strIconEmoji: A text string containing an Emoji reference (e.g. 31 | % ':bear:'), that Slack will use as the icon for the 32 | % notification. Note that strIconURL and strIconEmoji 33 | % should not both be provided. 34 | % 35 | % sAttachment: A Slack attachment structure, created by 36 | % 'MakeSlackAttachment'. Multiple attachments can be 37 | % provided in a cell array. 38 | % 39 | % Uses components of: 40 | % URLREAD2: http://www.mathworks.com/matlabcentral/fileexchange/35693-urlread2 41 | % JSONLAB: http://www.mathworks.com/matlabcentral/fileexchange/33381-jsonlab--a-toolbox-to-encode-decode-json-files-in-matlab-octave 42 | % 43 | % With thanks to Jim Hokanson and Qianqian Fang. 44 | 45 | % Author: Dylan Muir 46 | % Created: 19th November, 2014 47 | 48 | % -- Check arguments 49 | 50 | if (nargin < 2) 51 | help SendSlackNotification; 52 | error('*** SendSlackNotification: Incorrect usage.'); 53 | end 54 | 55 | 56 | % -- Create JSON payload structure 57 | 58 | % - Set up payload 59 | sPayload.text = strText; 60 | 61 | % - Add target channel or user 62 | if (exist('strTarget', 'var') && ~isempty(strTarget)) 63 | sPayload.channel = strTarget; 64 | end 65 | 66 | % - Define custom source user name 67 | if (exist('strUsername', 'var') && ~isempty(strUsername)) 68 | sPayload.username = strUsername; 69 | end 70 | 71 | % - Define custom icon (URL) 72 | if (exist('strIconURL', 'var') && ~isempty(strIconURL)) 73 | sPayload.icon_url = strIconURL; 74 | end 75 | 76 | % - Define custom icon (emoji) 77 | if (exist('strIconEmoji', 'var') && ~isempty(strIconEmoji)) 78 | sPayload.icon_emoji = strIconEmoji; 79 | end 80 | 81 | % - Add attachments 82 | if (exist('csAttachments', 'var') && ~isempty(csAttachments)) 83 | % - Accept a single attachment as a simple structure 84 | if (~iscell(csAttachments)) 85 | csAttachments = {csAttachments}; 86 | end 87 | 88 | % - Add a dummy attachment, if necessary (to ensure proper JSON 89 | % formatting) 90 | if (numel(csAttachments) == 1) 91 | csAttachments = [csAttachments MakeSlackAttachment('')]; 92 | end 93 | 94 | % - Include the attachments 95 | sPayload.attachments = csAttachments; 96 | end 97 | 98 | 99 | % -- Translate to JSON 100 | 101 | opt.NoRowBracket = 1; 102 | strJSON = savejson('', sPayload, opt); 103 | 104 | 105 | % -- Send to Slack using POST to the hook URL 106 | 107 | [strHTTPOutput, sHTTPExtra] = urlread2(strHookURL, 'POST', strJSON); 108 | 109 | 110 | % --- END of SendSlackNotification.m --- 111 | -------------------------------------------------------------------------------- /private/urlread2.m: -------------------------------------------------------------------------------- 1 | function [output,extras] = urlread2(urlChar,method,body,headersIn,varargin) 2 | %urlread2 Makes HTTP requests and processes response 3 | % 4 | % [output,extras] = urlread2(urlChar, *method, *body, *headersIn, varargin) 5 | % 6 | % * indicates optional inputs that must be entered in place 7 | % 8 | % UNDOCUMENTED MATLAB VERSION 9 | % 10 | % EXAMPLE CALLING FORMS 11 | % ... = urlread2(urlChar) 12 | % ... = urlread2(urlChar,'GET','',[],prop1,value1,prop2,value2,etc) 13 | % ... = urlread2(urlChar,'POST',body,headers) 14 | % 15 | % FEATURES 16 | % ======================================================================= 17 | % 1) Allows specification of any HTTP method 18 | % 2) Allows specification of any header. Very little is hard-coded 19 | % in for header handling. 20 | % 3) Returns response status and headers 21 | % 4) Should handle unicode properly ... 22 | % 23 | % OUTPUTS 24 | % ======================================================================= 25 | % output : body of the response, either text or binary depending upon 26 | % CAST_OUTPUT property 27 | % extras : (structure) 28 | % .allHeaders - stucture, fields have cellstr values, HTTP headers may 29 | % may be repeated but will have a single field entry, with each 30 | % repeat's value another being another entry in the cellstr, for 31 | % example: 32 | % .Set_Cookie = {'first_value' 'second_value'} 33 | % .firstHeaders - (structure), variable fields, contains the first 34 | % string entry for each field in allHeaders, this 35 | % structure can be used to avoid dereferencing a cell 36 | % for fields you expect not to be repeated ... 37 | % EXAMPLE: 38 | % .Response : 'HTTP/1.1 200 OK'} 39 | % .Server : 'nginx' 40 | % .Date : 'Tue, 29 Nov 2011 02:23:16 GMT' 41 | % .Content_Type : 'text/html; charset=UTF-8' 42 | % .Content_Length : '109155' 43 | % .Connection : 'keep-alive' 44 | % .Vary : 'Accept-Encoding, User-Agent' 45 | % .Cache_Control : 'max-age=60, private' 46 | % .Set_Cookie : 'first_value' 47 | % .status - (structure) 48 | % .value : numeric value of status, ex. 200 49 | % .msg : message that goes along with status, ex. 'OK' 50 | % .url - eventual url that led to output, this can change from 51 | % the input with redirects, see FOLLOW_REDIRECTS 52 | % .isGood - (logical) I believe this is an indicator of the presence of 400 53 | % or 500 status codes (see status.value) but more 54 | % testing is needed. In other words, true if status.value < 400. 55 | % In code, set true if the response was obtainable without 56 | % resorting to checking the error stream. 57 | % 58 | % INPUTS 59 | % ======================================================================= 60 | % urlChar : The full url, must include scheme (http, https) 61 | % method : examples: 'GET' 'POST' etc 62 | % body : (vector)(char, uint8 or int8) body to write, generally used 63 | % with POST or PUT, use of uint8 or int8 ensures that the 64 | % body input is not manipulated before sending, char is sent 65 | % via unicode2native function with ENCODING input (see below) 66 | % headersIn : (structure array), use empty [] or '' if no headers are needed 67 | % but varargin property/value pairs are, multiple headers 68 | % may be passed in as a structure array 69 | % .name - (string), name of the header, a name property is used 70 | % instead of a field because the name must match a valid 71 | % header 72 | % .value - (string), value to use 73 | % 74 | % OPTIONAL INPUTS (varargin, property/value pairs) 75 | % ======================================================================= 76 | % CAST_OUTPUT : (default true) output is uint8, useful if the body 77 | % of the response is not text 78 | % ENCODING : (default ''), ENCODING input to function unicode2native 79 | % FOLLOW_REDIRECTS : (default true), if false 3xx status codes will 80 | % be returned and need to be handled by the user, 81 | % note this does not handle javascript or meta tag 82 | % redirects, just server based ones 83 | % READ_TIMEOUT : (default 0), 0 means no timeout, value is in 84 | % milliseconds 85 | % 86 | % EXAMPLES 87 | % ======================================================================= 88 | % GET: 89 | % -------------------------------------------- 90 | % url = 'http://www.mathworks.com/matlabcentral/fileexchange/'; 91 | % query = 'urlread2'; 92 | % params = {'term' query}; 93 | % queryString = http_paramsToString(params,1); 94 | % url = [url '?' queryString]; 95 | % [output,extras] = urlread2(url); 96 | % 97 | % POST: 98 | % -------------------------------------------- 99 | % url = 'http://posttestserver.com/post.php'; 100 | % params = {'testChars' char([2500 30000]) 'new code' '?'}; 101 | % [paramString,header] = http_paramsToString(params,1); 102 | % [output,extras] = urlread2(url,'POST',paramString,header); 103 | % 104 | % From behind a firewall, use the Preferences to set your proxy server. 105 | % 106 | % See Also: 107 | % http_paramsToString 108 | % unicode2native 109 | % native2unicode 110 | % 111 | % Subfunctions: 112 | % fixHeaderCasing - small subfunction to fix case errors encountered in real 113 | % world, requires updating when casing doesn't match expected form, like 114 | % if someone sent the header content-Encoding instead of 115 | % Content-Encoding 116 | % 117 | % Based on original urlread code by Matthew J. Simoneau 118 | % 119 | % VERSION = 1.1 120 | 121 | in.CAST_OUTPUT = true; 122 | in.FOLLOW_REDIRECTS = true; 123 | in.READ_TIMEOUT = 0; 124 | in.ENCODING = ''; 125 | 126 | %Input handling 127 | %--------------------------------------- 128 | if ~isempty(varargin) 129 | for i = 1:2:numel(varargin) 130 | prop = upper(varargin{i}); 131 | value = varargin{i+1}; 132 | if isfield(in,prop) 133 | in.(prop) = value; 134 | else 135 | error('Unrecognized input to function: %s',prop) 136 | end 137 | end 138 | end 139 | 140 | if ~exist('method','var') || isempty(method), method = 'GET'; end 141 | if ~exist('body','var'), body = ''; end 142 | if ~exist('headersIn','var'), headersIn = []; end 143 | 144 | assert(usejava('jvm'),'Function requires Java') 145 | 146 | import com.mathworks.mlwidgets.io.InterruptibleStreamCopier; 147 | com.mathworks.mlwidgets.html.HTMLPrefs.setProxySettings %Proxy settings need to be set 148 | 149 | %Create a urlConnection. 150 | %----------------------------------- 151 | urlConnection = getURLConnection(urlChar); 152 | %For HTTP uses sun.net.www.protocol.http.HttpURLConnection 153 | %Might use ice.net.HttpURLConnection but this has more overhead 154 | 155 | %SETTING PROPERTIES 156 | %------------------------------------------------------- 157 | urlConnection.setRequestMethod(upper(method)); 158 | urlConnection.setFollowRedirects(in.FOLLOW_REDIRECTS); 159 | urlConnection.setReadTimeout(in.READ_TIMEOUT); 160 | 161 | for iHeader = 1:length(headersIn) 162 | curHeader = headersIn(iHeader); 163 | urlConnection.setRequestProperty(curHeader.name,curHeader.value); 164 | end 165 | 166 | if ~isempty(body) 167 | %Ensure vector? 168 | if size(body,1) > 1 169 | if size(body,2) > 1 170 | error('Input parameter to function: body, must be a vector') 171 | else 172 | body = body'; 173 | end 174 | end 175 | 176 | if ischar(body) 177 | %NOTE: '' defaults to Matlab's default encoding scheme 178 | body = unicode2native(body,in.ENCODING); 179 | elseif ~(strcmp(class(body),'uint8') || strcmp(class(body),'int8')) 180 | error('Function input: body, should be of class char, uint8, or int8, detected: %s',class(body)) 181 | end 182 | 183 | urlConnection.setRequestProperty('Content-Length',int2str(length(body))); 184 | urlConnection.setDoOutput(true); 185 | outputStream = urlConnection.getOutputStream; 186 | outputStream.write(body); 187 | outputStream.close; 188 | else 189 | urlConnection.setRequestProperty('Content-Length','0'); 190 | end 191 | 192 | %========================================================================== 193 | % Read the data from the connection. 194 | %========================================================================== 195 | %This should be done first because it tells us if things are ok or not 196 | %NOTE: If there is an error, functions below using urlConnection, notably 197 | %getResponseCode, will fail as well 198 | try 199 | inputStream = urlConnection.getInputStream; 200 | isGood = true; 201 | catch ME 202 | isGood = false; 203 | %NOTE: HTTP error codes will throw an error here, we'll allow those for now 204 | %We might also get another error in which case the inputStream will be 205 | %undefined, those we will throw here 206 | inputStream = urlConnection.getErrorStream; 207 | 208 | if isempty(inputStream) 209 | msg = ME.message; 210 | I = strfind(msg,char([13 10 9])); %see example by setting timeout to 1 211 | %Should remove the barf of the stack, at ... at ... at ... etc 212 | %Likely that this could be improved ... (generate link with full msg) 213 | if ~isempty(I) 214 | msg = msg(1:I(1)-1); 215 | end 216 | fprintf(2,'Response stream is undefined\n below is a Java Error dump (truncated):\n'); 217 | error(msg) 218 | end 219 | end 220 | 221 | %POPULATING HEADERS 222 | %-------------------------------------------------------------------------- 223 | allHeaders = struct; 224 | allHeaders.Response = {char(urlConnection.getHeaderField(0))}; 225 | done = false; 226 | headerIndex = 0; 227 | 228 | while ~done 229 | headerIndex = headerIndex + 1; 230 | headerValue = char(urlConnection.getHeaderField(headerIndex)); 231 | if ~isempty(headerValue) 232 | headerName = char(urlConnection.getHeaderFieldKey(headerIndex)); 233 | headerName = fixHeaderCasing(headerName); %NOT YET FINISHED 234 | 235 | %Important, for name safety all hyphens are replace with underscores 236 | headerName(headerName == '-') = '_'; 237 | if isfield(allHeaders,headerName) 238 | allHeaders.(headerName) = [allHeaders.(headerName) headerValue]; 239 | else 240 | allHeaders.(headerName) = {headerValue}; 241 | end 242 | else 243 | done = true; 244 | end 245 | end 246 | 247 | firstHeaders = struct; 248 | fn = fieldnames(allHeaders); 249 | for iHeader = 1:length(fn) 250 | curField = fn{iHeader}; 251 | firstHeaders.(curField) = allHeaders.(curField){1}; 252 | end 253 | 254 | status = struct(... 255 | 'value', urlConnection.getResponseCode(),... 256 | 'msg', char(urlConnection.getResponseMessage)); 257 | 258 | %PROCESSING OF OUTPUT 259 | %---------------------------------------------------------- 260 | byteArrayOutputStream = java.io.ByteArrayOutputStream; 261 | % This StreamCopier is unsupported and may change at any time. OH GREAT :/ 262 | isc = InterruptibleStreamCopier.getInterruptibleStreamCopier; 263 | isc.copyStream(inputStream,byteArrayOutputStream); 264 | inputStream.close; 265 | byteArrayOutputStream.close; 266 | 267 | if in.CAST_OUTPUT 268 | charset = ''; 269 | 270 | %Extraction of character set from Content-Type header if possible 271 | if isfield(firstHeaders,'Content_Type') 272 | text = firstHeaders.Content_Type; 273 | %Always open to regexp improvements 274 | charset = regexp(text,'(?<=charset=)[^\s]*','match','once'); 275 | end 276 | 277 | if ~isempty(charset) 278 | output = native2unicode(typecast(byteArrayOutputStream.toByteArray','uint8'),charset); 279 | else 280 | output = char(typecast(byteArrayOutputStream.toByteArray','uint8')); 281 | end 282 | else 283 | %uint8 is more useful for later charecter conversions 284 | %uint8 or int8 is somewhat arbitary at this point 285 | output = typecast(byteArrayOutputStream.toByteArray','uint8'); 286 | end 287 | 288 | extras = struct; 289 | extras.allHeaders = allHeaders; 290 | extras.firstHeaders = firstHeaders; 291 | extras.status = status; 292 | %Gets eventual url even with redirection 293 | extras.url = char(urlConnection.getURL); 294 | extras.isGood = isGood; 295 | 296 | 297 | 298 | end 299 | 300 | function headerNameOut = fixHeaderCasing(headerName) 301 | %fixHeaderCasing Forces standard casing of headers 302 | % 303 | % headerNameOut = fixHeaderCasing(headerName) 304 | % 305 | % This is important for field access in a structure which 306 | % is case sensitive 307 | % 308 | % Not yet finished. 309 | % I've been adding to this function as problems come along 310 | 311 | switch lower(headerName) 312 | case 'location' 313 | headerNameOut = 'Location'; 314 | case 'content_type' 315 | headerNameOut = 'Content_Type'; 316 | otherwise 317 | headerNameOut = headerName; 318 | end 319 | end 320 | 321 | %========================================================================== 322 | %========================================================================== 323 | %========================================================================== 324 | 325 | function urlConnection = getURLConnection(urlChar) 326 | %getURLConnection 327 | % 328 | % urlConnection = getURLConnection(urlChar) 329 | 330 | % Determine the protocol (before the ":"). 331 | protocol = urlChar(1:find(urlChar==':',1)-1); 332 | 333 | 334 | % Try to use the native handler, not the ice.* classes. 335 | try 336 | switch protocol 337 | case 'http' 338 | %http://www.docjar.com/docs/api/sun/net/www/protocol/http/HttpURLConnection.html 339 | handler = sun.net.www.protocol.http.Handler; 340 | case 'https' 341 | handler = sun.net.www.protocol.https.Handler; 342 | end 343 | catch ME 344 | handler = []; 345 | end 346 | 347 | % Create the URL object. 348 | try 349 | if isempty(handler) 350 | url = java.net.URL(urlChar); 351 | else 352 | url = java.net.URL([],urlChar,handler); 353 | end 354 | catch ME 355 | error('Failure to parse URL or protocol not supported for:\nURL: %s',urlChar); 356 | end 357 | 358 | % Get the proxy information using MathWorks facilities for unified proxy 359 | % preference settings. 360 | mwtcp = com.mathworks.net.transport.MWTransportClientPropertiesFactory.create(); 361 | proxy = mwtcp.getProxy(); 362 | 363 | % Open a connection to the URL. 364 | if isempty(proxy) 365 | urlConnection = url.openConnection; 366 | else 367 | urlConnection = url.openConnection(proxy); 368 | end 369 | 370 | 371 | end 372 | -------------------------------------------------------------------------------- /private/loadubjson.m: -------------------------------------------------------------------------------- 1 | function data = loadubjson(fname,varargin) 2 | % 3 | % data=loadubjson(fname,opt) 4 | % or 5 | % data=loadubjson(fname,'param1',value1,'param2',value2,...) 6 | % 7 | % parse a JSON (JavaScript Object Notation) file or string 8 | % 9 | % authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) 10 | % date: 2013/08/01 11 | % 12 | % $Id: loadubjson.m 436 2014-08-05 20:51:40Z fangq $ 13 | % 14 | % input: 15 | % fname: input file name, if fname contains "{}" or "[]", fname 16 | % will be interpreted as a UBJSON string 17 | % opt: a struct to store parsing options, opt can be replaced by 18 | % a list of ('param',value) pairs. The param string is equivallent 19 | % to a field in opt. 20 | % 21 | % output: 22 | % dat: a cell array, where {...} blocks are converted into cell arrays, 23 | % and [...] are converted to arrays 24 | % 25 | % license: 26 | % Simplified BSD License 27 | % 28 | % -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) 29 | % 30 | 31 | global pos inStr len esc index_esc len_esc isoct arraytoken fileendian systemendian 32 | 33 | if(regexp(fname,'[\{\}\]\[]','once')) 34 | string=fname; 35 | elseif(exist(fname,'file')) 36 | fid = fopen(fname,'rb'); 37 | string = fread(fid,inf,'uint8=>char')'; 38 | fclose(fid); 39 | else 40 | error('input file does not exist'); 41 | end 42 | 43 | pos = 1; len = length(string); inStr = string; 44 | isoct=exist('OCTAVE_VERSION','builtin'); 45 | arraytoken=find(inStr=='[' | inStr==']' | inStr=='"'); 46 | jstr=regexprep(inStr,'\\\\',' '); 47 | escquote=regexp(jstr,'\\"'); 48 | arraytoken=sort([arraytoken escquote]); 49 | 50 | % String delimiters and escape chars identified to improve speed: 51 | esc = find(inStr=='"' | inStr=='\' ); % comparable to: regexp(inStr, '["\\]'); 52 | index_esc = 1; len_esc = length(esc); 53 | 54 | opt=varargin2struct(varargin{:}); 55 | fileendian=upper(jsonopt('IntEndian','B',opt)); 56 | [os,maxelem,systemendian]=computer; 57 | 58 | jsoncount=1; 59 | while pos <= len 60 | switch(next_char) 61 | case '{' 62 | data{jsoncount} = parse_object(opt); 63 | case '[' 64 | data{jsoncount} = parse_array(opt); 65 | otherwise 66 | error_pos('Outer level structure must be an object or an array'); 67 | end 68 | jsoncount=jsoncount+1; 69 | end % while 70 | 71 | jsoncount=length(data); 72 | if(jsoncount==1 && iscell(data)) 73 | data=data{1}; 74 | end 75 | 76 | if(~isempty(data)) 77 | if(isstruct(data)) % data can be a struct array 78 | data=jstruct2array(data); 79 | elseif(iscell(data)) 80 | data=jcell2array(data); 81 | end 82 | end 83 | 84 | 85 | %% 86 | function newdata=parse_collection(id,data,obj) 87 | 88 | if(jsoncount>0 && exist('data','var')) 89 | if(~iscell(data)) 90 | newdata=cell(1); 91 | newdata{1}=data; 92 | data=newdata; 93 | end 94 | end 95 | 96 | %% 97 | function newdata=jcell2array(data) 98 | len=length(data); 99 | newdata=data; 100 | for i=1:len 101 | if(isstruct(data{i})) 102 | newdata{i}=jstruct2array(data{i}); 103 | elseif(iscell(data{i})) 104 | newdata{i}=jcell2array(data{i}); 105 | end 106 | end 107 | 108 | %%------------------------------------------------------------------------- 109 | function newdata=jstruct2array(data) 110 | fn=fieldnames(data); 111 | newdata=data; 112 | len=length(data); 113 | for i=1:length(fn) % depth-first 114 | for j=1:len 115 | if(isstruct(getfield(data(j),fn{i}))) 116 | newdata(j)=setfield(newdata(j),fn{i},jstruct2array(getfield(data(j),fn{i}))); 117 | end 118 | end 119 | end 120 | if(~isempty(strmatch('x0x5F_ArrayType_',fn)) && ~isempty(strmatch('x0x5F_ArrayData_',fn))) 121 | newdata=cell(len,1); 122 | for j=1:len 123 | ndata=cast(data(j).x0x5F_ArrayData_,data(j).x0x5F_ArrayType_); 124 | iscpx=0; 125 | if(~isempty(strmatch('x0x5F_ArrayIsComplex_',fn))) 126 | if(data(j).x0x5F_ArrayIsComplex_) 127 | iscpx=1; 128 | end 129 | end 130 | if(~isempty(strmatch('x0x5F_ArrayIsSparse_',fn))) 131 | if(data(j).x0x5F_ArrayIsSparse_) 132 | if(~isempty(strmatch('x0x5F_ArraySize_',fn))) 133 | dim=double(data(j).x0x5F_ArraySize_); 134 | if(iscpx && size(ndata,2)==4-any(dim==1)) 135 | ndata(:,end-1)=complex(ndata(:,end-1),ndata(:,end)); 136 | end 137 | if isempty(ndata) 138 | % All-zeros sparse 139 | ndata=sparse(dim(1),prod(dim(2:end))); 140 | elseif dim(1)==1 141 | % Sparse row vector 142 | ndata=sparse(1,ndata(:,1),ndata(:,2),dim(1),prod(dim(2:end))); 143 | elseif dim(2)==1 144 | % Sparse column vector 145 | ndata=sparse(ndata(:,1),1,ndata(:,2),dim(1),prod(dim(2:end))); 146 | else 147 | % Generic sparse array. 148 | ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3),dim(1),prod(dim(2:end))); 149 | end 150 | else 151 | if(iscpx && size(ndata,2)==4) 152 | ndata(:,3)=complex(ndata(:,3),ndata(:,4)); 153 | end 154 | ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3)); 155 | end 156 | end 157 | elseif(~isempty(strmatch('x0x5F_ArraySize_',fn))) 158 | if(iscpx && size(ndata,2)==2) 159 | ndata=complex(ndata(:,1),ndata(:,2)); 160 | end 161 | ndata=reshape(ndata(:),data(j).x0x5F_ArraySize_); 162 | end 163 | newdata{j}=ndata; 164 | end 165 | if(len==1) 166 | newdata=newdata{1}; 167 | end 168 | end 169 | 170 | %%------------------------------------------------------------------------- 171 | function object = parse_object(varargin) 172 | parse_char('{'); 173 | object = []; 174 | type=''; 175 | count=-1; 176 | if(next_char == '$') 177 | type=inStr(pos+1); % TODO 178 | pos=pos+2; 179 | end 180 | if(next_char == '#') 181 | pos=pos+1; 182 | count=double(parse_number()); 183 | end 184 | if next_char ~= '}' 185 | num=0; 186 | while 1 187 | str = parseStr(varargin{:}); 188 | if isempty(str) 189 | error_pos('Name of value at position %d cannot be empty'); 190 | end 191 | %parse_char(':'); 192 | val = parse_value(varargin{:}); 193 | num=num+1; 194 | eval( sprintf( 'object.%s = val;', valid_field(str) ) ); 195 | if next_char == '}' || (count>=0 && num>=count) 196 | break; 197 | end 198 | %parse_char(','); 199 | end 200 | end 201 | if(count==-1) 202 | parse_char('}'); 203 | end 204 | 205 | %%------------------------------------------------------------------------- 206 | function [cid,len]=elem_info(type) 207 | id=strfind('iUIlLdD',type); 208 | dataclass={'int8','uint8','int16','int32','int64','single','double'}; 209 | bytelen=[1,1,2,4,8,4,8]; 210 | if(id>0) 211 | cid=dataclass{id}; 212 | len=bytelen(id); 213 | else 214 | error_pos('unsupported type at position %d'); 215 | end 216 | %%------------------------------------------------------------------------- 217 | 218 | 219 | function [data adv]=parse_block(type,count,varargin) 220 | global pos inStr isoct fileendian systemendian 221 | [cid,len]=elem_info(type); 222 | datastr=inStr(pos:pos+len*count-1); 223 | if(isoct) 224 | newdata=int8(datastr); 225 | else 226 | newdata=uint8(datastr); 227 | end 228 | id=strfind('iUIlLdD',type); 229 | if(id<=5 && fileendian~=systemendian) 230 | newdata=swapbytes(typecast(newdata,cid)); 231 | end 232 | data=typecast(newdata,cid); 233 | adv=double(len*count); 234 | 235 | %%------------------------------------------------------------------------- 236 | 237 | 238 | function object = parse_array(varargin) % JSON array is written in row-major order 239 | global pos inStr isoct 240 | parse_char('['); 241 | object = cell(0, 1); 242 | dim=[]; 243 | type=''; 244 | count=-1; 245 | if(next_char == '$') 246 | type=inStr(pos+1); 247 | pos=pos+2; 248 | end 249 | if(next_char == '#') 250 | pos=pos+1; 251 | if(next_char=='[') 252 | dim=parse_array(varargin{:}); 253 | count=prod(double(dim)); 254 | else 255 | count=double(parse_number()); 256 | end 257 | end 258 | if(~isempty(type)) 259 | if(count>=0) 260 | [object adv]=parse_block(type,count,varargin{:}); 261 | if(~isempty(dim)) 262 | object=reshape(object,dim); 263 | end 264 | pos=pos+adv; 265 | return; 266 | else 267 | endpos=matching_bracket(inStr,pos); 268 | [cid,len]=elem_info(type); 269 | count=(endpos-pos)/len; 270 | [object adv]=parse_block(type,count,varargin{:}); 271 | pos=pos+adv; 272 | parse_char(']'); 273 | return; 274 | end 275 | end 276 | if next_char ~= ']' 277 | while 1 278 | val = parse_value(varargin{:}); 279 | object{end+1} = val; 280 | if next_char == ']' 281 | break; 282 | end 283 | %parse_char(','); 284 | end 285 | end 286 | if(jsonopt('SimplifyCell',0,varargin{:})==1) 287 | try 288 | oldobj=object; 289 | object=cell2mat(object')'; 290 | if(iscell(oldobj) && isstruct(object) && numel(object)>1 && jsonopt('SimplifyCellArray',1,varargin{:})==0) 291 | object=oldobj; 292 | elseif(size(object,1)>1 && ndims(object)==2) 293 | object=object'; 294 | end 295 | catch 296 | end 297 | end 298 | if(count==-1) 299 | parse_char(']'); 300 | end 301 | 302 | %%------------------------------------------------------------------------- 303 | 304 | function parse_char(c) 305 | global pos inStr len 306 | skip_whitespace; 307 | if pos > len || inStr(pos) ~= c 308 | error_pos(sprintf('Expected %c at position %%d', c)); 309 | else 310 | pos = pos + 1; 311 | skip_whitespace; 312 | end 313 | 314 | %%------------------------------------------------------------------------- 315 | 316 | function c = next_char 317 | global pos inStr len 318 | skip_whitespace; 319 | if pos > len 320 | c = []; 321 | else 322 | c = inStr(pos); 323 | end 324 | 325 | %%------------------------------------------------------------------------- 326 | 327 | function skip_whitespace 328 | global pos inStr len 329 | while pos <= len && isspace(inStr(pos)) 330 | pos = pos + 1; 331 | end 332 | 333 | %%------------------------------------------------------------------------- 334 | function str = parseStr(varargin) 335 | global pos inStr esc index_esc len_esc 336 | % len, ns = length(inStr), keyboard 337 | type=inStr(pos); 338 | if type ~= 'S' && type ~= 'C' && type ~= 'H' 339 | error_pos('String starting with S expected at position %d'); 340 | else 341 | pos = pos + 1; 342 | end 343 | if(type == 'C') 344 | str=inStr(pos); 345 | pos=pos+1; 346 | return; 347 | end 348 | bytelen=double(parse_number()); 349 | if(length(inStr)>=pos+bytelen-1) 350 | str=inStr(pos:pos+bytelen-1); 351 | pos=pos+bytelen; 352 | else 353 | error_pos('End of file while expecting end of inStr'); 354 | end 355 | 356 | %%------------------------------------------------------------------------- 357 | 358 | function num = parse_number(varargin) 359 | global pos inStr len isoct fileendian systemendian 360 | id=strfind('iUIlLdD',inStr(pos)); 361 | if(isempty(id)) 362 | error_pos('expecting a number at position %d'); 363 | end 364 | type={'int8','uint8','int16','int32','int64','single','double'}; 365 | bytelen=[1,1,2,4,8,4,8]; 366 | datastr=inStr(pos+1:pos+bytelen(id)); 367 | if(isoct) 368 | newdata=int8(datastr); 369 | else 370 | newdata=uint8(datastr); 371 | end 372 | if(id<=5 && fileendian~=systemendian) 373 | newdata=swapbytes(typecast(newdata,type{id})); 374 | end 375 | num=typecast(newdata,type{id}); 376 | pos = pos + bytelen(id)+1; 377 | 378 | %%------------------------------------------------------------------------- 379 | 380 | function val = parse_value(varargin) 381 | global pos inStr len 382 | true = 1; false = 0; 383 | 384 | switch(inStr(pos)) 385 | case {'S','C','H'} 386 | val = parseStr(varargin{:}); 387 | return; 388 | case '[' 389 | val = parse_array(varargin{:}); 390 | return; 391 | case '{' 392 | val = parse_object(varargin{:}); 393 | if isstruct(val) 394 | if(~isempty(strmatch('x0x5F_ArrayType_',fieldnames(val), 'exact'))) 395 | val=jstruct2array(val); 396 | end 397 | elseif isempty(val) 398 | val = struct; 399 | end 400 | return; 401 | case {'i','U','I','l','L','d','D'} 402 | val = parse_number(varargin{:}); 403 | return; 404 | case 'T' 405 | val = true; 406 | pos = pos + 1; 407 | return; 408 | case 'F' 409 | val = false; 410 | pos = pos + 1; 411 | return; 412 | case {'Z','N'} 413 | val = []; 414 | pos = pos + 1; 415 | return; 416 | end 417 | error_pos('Value expected at position %d'); 418 | %%------------------------------------------------------------------------- 419 | 420 | function error_pos(msg) 421 | global pos inStr len 422 | poShow = max(min([pos-15 pos-1 pos pos+20],len),1); 423 | if poShow(3) == poShow(2) 424 | poShow(3:4) = poShow(2)+[0 -1]; % display nothing after 425 | end 426 | msg = [sprintf(msg, pos) ': ' ... 427 | inStr(poShow(1):poShow(2)) '' inStr(poShow(3):poShow(4)) ]; 428 | error( ['JSONparser:invalidFormat: ' msg] ); 429 | 430 | %%------------------------------------------------------------------------- 431 | 432 | function str = valid_field(str) 433 | global isoct 434 | % From MATLAB doc: field names must begin with a letter, which may be 435 | % followed by any combination of letters, digits, and underscores. 436 | % Invalid characters will be converted to underscores, and the prefix 437 | % "x0x[Hex code]_" will be added if the first character is not a letter. 438 | pos=regexp(str,'^[^A-Za-z]','once'); 439 | if(~isempty(pos)) 440 | if(~isoct) 441 | str=regexprep(str,'^([^A-Za-z])','x0x${sprintf(''%X'',unicode2native($1))}_','once'); 442 | else 443 | str=sprintf('x0x%X_%s',char(str(1)),str(2:end)); 444 | end 445 | end 446 | if(isempty(regexp(str,'[^0-9A-Za-z_]', 'once' ))) return; end 447 | if(~isoct) 448 | str=regexprep(str,'([^0-9A-Za-z_])','_0x${sprintf(''%X'',unicode2native($1))}_'); 449 | else 450 | pos=regexp(str,'[^0-9A-Za-z_]'); 451 | if(isempty(pos)) return; end 452 | str0=str; 453 | pos0=[0 pos(:)' length(str)]; 454 | str=''; 455 | for i=1:length(pos) 456 | str=[str str0(pos0(i)+1:pos(i)-1) sprintf('_0x%X_',str0(pos(i)))]; 457 | end 458 | if(pos(end)~=length(str)) 459 | str=[str str0(pos0(end-1)+1:pos0(end))]; 460 | end 461 | end 462 | %str(~isletter(str) & ~('0' <= str & str <= '9')) = '_'; 463 | 464 | %%------------------------------------------------------------------------- 465 | function endpos = matching_quote(str,pos) 466 | len=length(str); 467 | while(pos1 && str(pos-1)=='\')) 470 | endpos=pos; 471 | return; 472 | end 473 | end 474 | pos=pos+1; 475 | end 476 | error('unmatched quotation mark'); 477 | %%------------------------------------------------------------------------- 478 | function [endpos e1l e1r maxlevel] = matching_bracket(str,pos) 479 | global arraytoken 480 | level=1; 481 | maxlevel=level; 482 | endpos=0; 483 | bpos=arraytoken(arraytoken>=pos); 484 | tokens=str(bpos); 485 | len=length(tokens); 486 | pos=1; 487 | e1l=[]; 488 | e1r=[]; 489 | while(pos<=len) 490 | c=tokens(pos); 491 | if(c==']') 492 | level=level-1; 493 | if(isempty(e1r)) e1r=bpos(pos); end 494 | if(level==0) 495 | endpos=bpos(pos); 496 | return 497 | end 498 | end 499 | if(c=='[') 500 | if(isempty(e1l)) e1l=bpos(pos); end 501 | level=level+1; 502 | maxlevel=max(maxlevel,level); 503 | end 504 | if(c=='"') 505 | pos=matching_quote(tokens,pos+1); 506 | end 507 | pos=pos+1; 508 | end 509 | if(endpos==0) 510 | error('unmatched "]"'); 511 | end 512 | 513 | -------------------------------------------------------------------------------- /private/saveubjson.m: -------------------------------------------------------------------------------- 1 | function json=saveubjson(rootname,obj,varargin) 2 | % 3 | % json=saveubjson(rootname,obj,filename) 4 | % or 5 | % json=saveubjson(rootname,obj,opt) 6 | % json=saveubjson(rootname,obj,'param1',value1,'param2',value2,...) 7 | % 8 | % convert a MATLAB object (cell, struct or array) into a Universal 9 | % Binary JSON (UBJSON) binary string 10 | % 11 | % author: Qianqian Fang (fangq nmr.mgh.harvard.edu) 12 | % created on 2013/08/17 13 | % 14 | % $Id: saveubjson.m 440 2014-09-17 19:59:45Z fangq $ 15 | % 16 | % input: 17 | % rootname: name of the root-object, if set to '', will use variable name 18 | % obj: a MATLAB object (array, cell, cell array, struct, struct array) 19 | % filename: a string for the file name to save the output JSON data 20 | % opt: a struct for additional options, use [] if all use default 21 | % opt can have the following fields (first in [.|.] is the default) 22 | % 23 | % opt.FileName [''|string]: a file name to save the output JSON data 24 | % opt.ArrayToStruct[0|1]: when set to 0, saveubjson outputs 1D/2D 25 | % array in JSON array format; if sets to 1, an 26 | % array will be shown as a struct with fields 27 | % "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for 28 | % sparse arrays, the non-zero elements will be 29 | % saved to _ArrayData_ field in triplet-format i.e. 30 | % (ix,iy,val) and "_ArrayIsSparse_" will be added 31 | % with a value of 1; for a complex array, the 32 | % _ArrayData_ array will include two columns 33 | % (4 for sparse) to record the real and imaginary 34 | % parts, and also "_ArrayIsComplex_":1 is added. 35 | % opt.ParseLogical [1|0]: if this is set to 1, logical array elem 36 | % will use true/false rather than 1/0. 37 | % opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single 38 | % numerical element will be shown without a square 39 | % bracket, unless it is the root object; if 0, square 40 | % brackets are forced for any numerical arrays. 41 | % opt.ForceRootName [0|1]: when set to 1 and rootname is empty, saveubjson 42 | % will use the name of the passed obj variable as the 43 | % root object name; if obj is an expression and 44 | % does not have a name, 'root' will be used; if this 45 | % is set to 0 and rootname is empty, the root level 46 | % will be merged down to the lower level. 47 | % opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), 48 | % for example, if opt.JSON='foo', the JSON data is 49 | % wrapped inside a function call as 'foo(...);' 50 | % opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson 51 | % back to the string form 52 | % opt can be replaced by a list of ('param',value) pairs. The param 53 | % string is equivallent to a field in opt. 54 | % output: 55 | % json: a string in the JSON format (see http://json.org) 56 | % 57 | % examples: 58 | % a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... 59 | % 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); 60 | % saveubjson('mesh',a) 61 | % saveubjson('mesh',a,'meshdata.ubj') 62 | % 63 | % license: 64 | % Simplified BSD License 65 | % 66 | % -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) 67 | % 68 | 69 | if(nargin==1) 70 | varname=inputname(1); 71 | obj=rootname; 72 | if(isempty(varname)) 73 | varname='root'; 74 | end 75 | rootname=varname; 76 | else 77 | varname=inputname(2); 78 | end 79 | if(length(varargin)==1 && ischar(varargin{1})) 80 | opt=struct('FileName',varargin{1}); 81 | else 82 | opt=varargin2struct(varargin{:}); 83 | end 84 | opt.IsOctave=exist('OCTAVE_VERSION','builtin'); 85 | rootisarray=0; 86 | rootlevel=1; 87 | forceroot=jsonopt('ForceRootName',0,opt); 88 | if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0) 89 | rootisarray=1; 90 | rootlevel=0; 91 | else 92 | if(isempty(rootname)) 93 | rootname=varname; 94 | end 95 | end 96 | if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot) 97 | rootname='root'; 98 | end 99 | json=obj2ubjson(rootname,obj,rootlevel,opt); 100 | if(~rootisarray) 101 | json=['{' json '}']; 102 | end 103 | 104 | jsonp=jsonopt('JSONP','',opt); 105 | if(~isempty(jsonp)) 106 | json=[jsonp '(' json ')']; 107 | end 108 | 109 | % save to a file if FileName is set, suggested by Patrick Rapin 110 | if(~isempty(jsonopt('FileName','',opt))) 111 | fid = fopen(opt.FileName, 'wb'); 112 | fwrite(fid,json); 113 | fclose(fid); 114 | end 115 | 116 | %%------------------------------------------------------------------------- 117 | function txt=obj2ubjson(name,item,level,varargin) 118 | 119 | if(iscell(item)) 120 | txt=cell2ubjson(name,item,level,varargin{:}); 121 | elseif(isstruct(item)) 122 | txt=struct2ubjson(name,item,level,varargin{:}); 123 | elseif(ischar(item)) 124 | txt=str2ubjson(name,item,level,varargin{:}); 125 | else 126 | txt=mat2ubjson(name,item,level,varargin{:}); 127 | end 128 | 129 | %%------------------------------------------------------------------------- 130 | function txt=cell2ubjson(name,item,level,varargin) 131 | txt=''; 132 | if(~iscell(item)) 133 | error('input is not a cell'); 134 | end 135 | 136 | dim=size(item); 137 | if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now 138 | item=reshape(item,dim(1),numel(item)/dim(1)); 139 | dim=size(item); 140 | end 141 | len=numel(item); % let's handle 1D cell first 142 | if(len>1) 143 | if(~isempty(name)) 144 | txt=[S_(checkname(name,varargin{:})) '[']; name=''; 145 | else 146 | txt='['; 147 | end 148 | elseif(len==0) 149 | if(~isempty(name)) 150 | txt=[S_(checkname(name,varargin{:})) 'Z']; name=''; 151 | else 152 | txt='Z'; 153 | end 154 | end 155 | for j=1:dim(2) 156 | if(dim(1)>1) txt=[txt '[']; end 157 | for i=1:dim(1) 158 | txt=[txt obj2ubjson(name,item{i,j},level+(len>1),varargin{:})]; 159 | end 160 | if(dim(1)>1) txt=[txt ']']; end 161 | end 162 | if(len>1) txt=[txt ']']; end 163 | 164 | %%------------------------------------------------------------------------- 165 | function txt=struct2ubjson(name,item,level,varargin) 166 | txt=''; 167 | if(~isstruct(item)) 168 | error('input is not a struct'); 169 | end 170 | dim=size(item); 171 | if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now 172 | item=reshape(item,dim(1),numel(item)/dim(1)); 173 | dim=size(item); 174 | end 175 | len=numel(item); 176 | 177 | if(~isempty(name)) 178 | if(len>1) txt=[S_(checkname(name,varargin{:})) '[']; end 179 | else 180 | if(len>1) txt='['; end 181 | end 182 | for j=1:dim(2) 183 | if(dim(1)>1) txt=[txt '[']; end 184 | for i=1:dim(1) 185 | names = fieldnames(item(i,j)); 186 | if(~isempty(name) && len==1) 187 | txt=[txt S_(checkname(name,varargin{:})) '{']; 188 | else 189 | txt=[txt '{']; 190 | end 191 | if(~isempty(names)) 192 | for e=1:length(names) 193 | txt=[txt obj2ubjson(names{e},getfield(item(i,j),... 194 | names{e}),level+(dim(1)>1)+1+(len>1),varargin{:})]; 195 | end 196 | end 197 | txt=[txt '}']; 198 | end 199 | if(dim(1)>1) txt=[txt ']']; end 200 | end 201 | if(len>1) txt=[txt ']']; end 202 | 203 | %%------------------------------------------------------------------------- 204 | function txt=str2ubjson(name,item,level,varargin) 205 | txt=''; 206 | if(~ischar(item)) 207 | error('input is not a string'); 208 | end 209 | item=reshape(item, max(size(item),[1 0])); 210 | len=size(item,1); 211 | 212 | if(~isempty(name)) 213 | if(len>1) txt=[S_(checkname(name,varargin{:})) '[']; end 214 | else 215 | if(len>1) txt='['; end 216 | end 217 | isoct=jsonopt('IsOctave',0,varargin{:}); 218 | for e=1:len 219 | val=item(e,:); 220 | if(len==1) 221 | obj=['' S_(checkname(name,varargin{:})) '' '',S_(val),'']; 222 | if(isempty(name)) obj=['',S_(val),'']; end 223 | txt=[txt,'',obj]; 224 | else 225 | txt=[txt,'',['',S_(val),'']]; 226 | end 227 | end 228 | if(len>1) txt=[txt ']']; end 229 | 230 | %%------------------------------------------------------------------------- 231 | function txt=mat2ubjson(name,item,level,varargin) 232 | if(~isnumeric(item) && ~islogical(item)) 233 | error('input is not an array'); 234 | end 235 | 236 | if(length(size(item))>2 || issparse(item) || ~isreal(item) || ... 237 | isempty(item) || jsonopt('ArrayToStruct',0,varargin{:})) 238 | cid=I_(uint32(max(size(item)))); 239 | if(isempty(name)) 240 | txt=['{' S_('_ArrayType_'),S_(class(item)),S_('_ArraySize_'),I_a(size(item),cid(1)) ]; 241 | else 242 | if(isempty(item)) 243 | txt=[S_(checkname(name,varargin{:})),'Z']; 244 | return; 245 | else 246 | txt=[S_(checkname(name,varargin{:})),'{',S_('_ArrayType_'),S_(class(item)),S_('_ArraySize_'),I_a(size(item),cid(1))]; 247 | end 248 | end 249 | else 250 | if(isempty(name)) 251 | txt=matdata2ubjson(item,level+1,varargin{:}); 252 | else 253 | if(numel(item)==1 && jsonopt('NoRowBracket',1,varargin{:})==1) 254 | numtxt=regexprep(regexprep(matdata2ubjson(item,level+1,varargin{:}),'^\[',''),']',''); 255 | txt=[S_(checkname(name,varargin{:})) numtxt]; 256 | else 257 | txt=[S_(checkname(name,varargin{:})),matdata2ubjson(item,level+1,varargin{:})]; 258 | end 259 | end 260 | return; 261 | end 262 | if(issparse(item)) 263 | [ix,iy]=find(item); 264 | data=full(item(find(item))); 265 | if(~isreal(item)) 266 | data=[real(data(:)),imag(data(:))]; 267 | if(size(item,1)==1) 268 | % Kludge to have data's 'transposedness' match item's. 269 | % (Necessary for complex row vector handling below.) 270 | data=data'; 271 | end 272 | txt=[txt,S_('_ArrayIsComplex_'),'T']; 273 | end 274 | txt=[txt,S_('_ArrayIsSparse_'),'T']; 275 | if(size(item,1)==1) 276 | % Row vector, store only column indices. 277 | txt=[txt,S_('_ArrayData_'),... 278 | matdata2ubjson([iy(:),data'],level+2,varargin{:})]; 279 | elseif(size(item,2)==1) 280 | % Column vector, store only row indices. 281 | txt=[txt,S_('_ArrayData_'),... 282 | matdata2ubjson([ix,data],level+2,varargin{:})]; 283 | else 284 | % General case, store row and column indices. 285 | txt=[txt,S_('_ArrayData_'),... 286 | matdata2ubjson([ix,iy,data],level+2,varargin{:})]; 287 | end 288 | else 289 | if(isreal(item)) 290 | txt=[txt,S_('_ArrayData_'),... 291 | matdata2ubjson(item(:)',level+2,varargin{:})]; 292 | else 293 | txt=[txt,S_('_ArrayIsComplex_'),'T']; 294 | txt=[txt,S_('_ArrayData_'),... 295 | matdata2ubjson([real(item(:)) imag(item(:))],level+2,varargin{:})]; 296 | end 297 | end 298 | txt=[txt,'}']; 299 | 300 | %%------------------------------------------------------------------------- 301 | function txt=matdata2ubjson(mat,level,varargin) 302 | if(isempty(mat)) 303 | txt='Z'; 304 | return; 305 | end 306 | if(size(mat,1)==1) 307 | level=level-1; 308 | end 309 | type=''; 310 | hasnegtive=(mat<0); 311 | if(isa(mat,'integer') || isinteger(mat) || (isfloat(mat) && all(mod(mat(:),1) == 0))) 312 | if(isempty(hasnegtive)) 313 | if(max(mat(:))<=2^8) 314 | type='U'; 315 | end 316 | end 317 | if(isempty(type)) 318 | % todo - need to consider negative ones separately 319 | id= histc(abs(max(mat(:))),[0 2^7 2^15 2^31 2^63]); 320 | if(isempty(find(id))) 321 | error('high-precision data is not yet supported'); 322 | end 323 | key='iIlL'; 324 | type=key(find(id)); 325 | end 326 | txt=[I_a(mat(:),type,size(mat))]; 327 | elseif(islogical(mat)) 328 | logicalval='FT'; 329 | if(numel(mat)==1) 330 | txt=logicalval(mat+1); 331 | else 332 | txt=['[$U#' I_a(size(mat),'l') typecast(swapbytes(uint8(mat(:)')),'uint8')]; 333 | end 334 | else 335 | if(numel(mat)==1) 336 | txt=['[' D_(mat) ']']; 337 | else 338 | txt=D_a(mat(:),'D',size(mat)); 339 | end 340 | end 341 | 342 | %txt=regexprep(mat2str(mat),'\s+',','); 343 | %txt=regexprep(txt,';',sprintf('],[')); 344 | % if(nargin>=2 && size(mat,1)>1) 345 | % txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']); 346 | % end 347 | if(any(isinf(mat(:)))) 348 | txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',varargin{:})); 349 | end 350 | if(any(isnan(mat(:)))) 351 | txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',varargin{:})); 352 | end 353 | 354 | %%------------------------------------------------------------------------- 355 | function newname=checkname(name,varargin) 356 | isunpack=jsonopt('UnpackHex',1,varargin{:}); 357 | newname=name; 358 | if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once'))) 359 | return 360 | end 361 | if(isunpack) 362 | isoct=jsonopt('IsOctave',0,varargin{:}); 363 | if(~isoct) 364 | newname=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}'); 365 | else 366 | pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start'); 367 | pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end'); 368 | if(isempty(pos)) return; end 369 | str0=name; 370 | pos0=[0 pend(:)' length(name)]; 371 | newname=''; 372 | for i=1:length(pos) 373 | newname=[newname str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))]; 374 | end 375 | if(pos(end)~=length(name)) 376 | newname=[newname str0(pos0(end-1)+1:pos0(end))]; 377 | end 378 | end 379 | end 380 | %%------------------------------------------------------------------------- 381 | function val=S_(str) 382 | if(length(str)==1) 383 | val=['C' str]; 384 | else 385 | val=['S' I_(int32(length(str))) str]; 386 | end 387 | %%------------------------------------------------------------------------- 388 | function val=I_(num) 389 | if(~isinteger(num)) 390 | error('input is not an integer'); 391 | end 392 | if(num>=0 && num<255) 393 | val=['U' data2byte(swapbytes(cast(num,'uint8')),'uint8')]; 394 | return; 395 | end 396 | key='iIlL'; 397 | cid={'int8','int16','int32','int64'}; 398 | for i=1:4 399 | if((num>0 && num<2^(i*8-1)) || (num<0 && num>=-2^(i*8-1))) 400 | val=[key(i) data2byte(swapbytes(cast(num,cid{i})),'uint8')]; 401 | return; 402 | end 403 | end 404 | error('unsupported integer'); 405 | 406 | %%------------------------------------------------------------------------- 407 | function val=D_(num) 408 | if(~isfloat(num)) 409 | error('input is not a float'); 410 | end 411 | 412 | if(isa(num,'single')) 413 | val=['d' data2byte(num,'uint8')]; 414 | else 415 | val=['D' data2byte(num,'uint8')]; 416 | end 417 | %%------------------------------------------------------------------------- 418 | function data=I_a(num,type,dim,format) 419 | id=find(ismember('iUIlL',type)); 420 | 421 | if(id==0) 422 | error('unsupported integer array'); 423 | end 424 | 425 | % based on UBJSON specs, all integer types are stored in big endian format 426 | 427 | if(id==1) 428 | data=data2byte(swapbytes(int8(num)),'uint8'); 429 | blen=1; 430 | elseif(id==2) 431 | data=data2byte(swapbytes(uint8(num)),'uint8'); 432 | blen=1; 433 | elseif(id==3) 434 | data=data2byte(swapbytes(int16(num)),'uint8'); 435 | blen=2; 436 | elseif(id==4) 437 | data=data2byte(swapbytes(int32(num)),'uint8'); 438 | blen=4; 439 | elseif(id==5) 440 | data=data2byte(swapbytes(int64(num)),'uint8'); 441 | blen=8; 442 | end 443 | 444 | if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2)) 445 | format='opt'; 446 | end 447 | if((nargin<4 || strcmp(format,'opt')) && numel(num)>1) 448 | if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2)))) 449 | cid=I_(uint32(max(dim))); 450 | data=['$' type '#' I_a(dim,cid(1)) data(:)']; 451 | else 452 | data=['$' type '#' I_(int32(numel(data)/blen)) data(:)']; 453 | end 454 | data=['[' data(:)']; 455 | else 456 | data=reshape(data,blen,numel(data)/blen); 457 | data(2:blen+1,:)=data; 458 | data(1,:)=type; 459 | data=data(:)'; 460 | data=['[' data(:)' ']']; 461 | end 462 | %%------------------------------------------------------------------------- 463 | function data=D_a(num,type,dim,format) 464 | id=find(ismember('dD',type)); 465 | 466 | if(id==0) 467 | error('unsupported float array'); 468 | end 469 | 470 | if(id==1) 471 | data=data2byte(single(num),'uint8'); 472 | elseif(id==2) 473 | data=data2byte(double(num),'uint8'); 474 | end 475 | 476 | if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2)) 477 | format='opt'; 478 | end 479 | if((nargin<4 || strcmp(format,'opt')) && numel(num)>1) 480 | if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2)))) 481 | cid=I_(uint32(max(dim))); 482 | data=['$' type '#' I_a(dim,cid(1)) data(:)']; 483 | else 484 | data=['$' type '#' I_(int32(numel(data)/(id*4))) data(:)']; 485 | end 486 | data=['[' data]; 487 | else 488 | data=reshape(data,(id*4),length(data)/(id*4)); 489 | data(2:(id*4+1),:)=data; 490 | data(1,:)=type; 491 | data=data(:)'; 492 | data=['[' data(:)' ']']; 493 | end 494 | %%------------------------------------------------------------------------- 495 | function bytes=data2byte(varargin) 496 | bytes=typecast(varargin{:}); 497 | bytes=bytes(:)'; 498 | -------------------------------------------------------------------------------- /private/savejson.m: -------------------------------------------------------------------------------- 1 | function json=savejson(rootname,obj,varargin) 2 | % 3 | % json=savejson(rootname,obj,filename) 4 | % or 5 | % json=savejson(rootname,obj,opt) 6 | % json=savejson(rootname,obj,'param1',value1,'param2',value2,...) 7 | % 8 | % convert a MATLAB object (cell, struct or array) into a JSON (JavaScript 9 | % Object Notation) string 10 | % 11 | % author: Qianqian Fang (fangq nmr.mgh.harvard.edu) 12 | % created on 2011/09/09 13 | % 14 | % $Id: savejson.m 439 2014-09-17 05:31:08Z fangq $ 15 | % 16 | % input: 17 | % rootname: name of the root-object, if set to '', will use variable name 18 | % obj: a MATLAB object (array, cell, cell array, struct, struct array) 19 | % filename: a string for the file name to save the output JSON data 20 | % opt: a struct for additional options, use [] if all use default 21 | % opt can have the following fields (first in [.|.] is the default) 22 | % 23 | % opt.FileName [''|string]: a file name to save the output JSON data 24 | % opt.FloatFormat ['%.10g'|string]: format to show each numeric element 25 | % of a 1D/2D array; 26 | % opt.ArrayIndent [1|0]: if 1, output explicit data array with 27 | % precedent indentation; if 0, no indentation 28 | % opt.ArrayToStruct[0|1]: when set to 0, savejson outputs 1D/2D 29 | % array in JSON array format; if sets to 1, an 30 | % array will be shown as a struct with fields 31 | % "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for 32 | % sparse arrays, the non-zero elements will be 33 | % saved to _ArrayData_ field in triplet-format i.e. 34 | % (ix,iy,val) and "_ArrayIsSparse_" will be added 35 | % with a value of 1; for a complex array, the 36 | % _ArrayData_ array will include two columns 37 | % (4 for sparse) to record the real and imaginary 38 | % parts, and also "_ArrayIsComplex_":1 is added. 39 | % opt.ParseLogical [0|1]: if this is set to 1, logical array elem 40 | % will use true/false rather than 1/0. 41 | % opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single 42 | % numerical element will be shown without a square 43 | % bracket, unless it is the root object; if 0, square 44 | % brackets are forced for any numerical arrays. 45 | % opt.ForceRootName [0|1]: when set to 1 and rootname is empty, savejson 46 | % will use the name of the passed obj variable as the 47 | % root object name; if obj is an expression and 48 | % does not have a name, 'root' will be used; if this 49 | % is set to 0 and rootname is empty, the root level 50 | % will be merged down to the lower level. 51 | % opt.Inf ['"$1_Inf_"'|string]: a customized regular expression pattern 52 | % to represent +/-Inf. The matched pattern is '([-+]*)Inf' 53 | % and $1 represents the sign. For those who want to use 54 | % 1e999 to represent Inf, they can set opt.Inf to '$11e999' 55 | % opt.NaN ['"_NaN_"'|string]: a customized regular expression pattern 56 | % to represent NaN 57 | % opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), 58 | % for example, if opt.JSON='foo', the JSON data is 59 | % wrapped inside a function call as 'foo(...);' 60 | % opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson 61 | % back to the string form 62 | % opt.SaveBinary [0|1]: 1 - save the JSON file in binary mode; 0 - text mode. 63 | % opt can be replaced by a list of ('param',value) pairs. The param 64 | % string is equivallent to a field in opt. 65 | % output: 66 | % json: a string in the JSON format (see http://json.org) 67 | % 68 | % examples: 69 | % a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... 70 | % 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); 71 | % savejson('mesh',a) 72 | % savejson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g') 73 | % 74 | % license: 75 | % Simplified BSD License 76 | % 77 | % -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) 78 | % 79 | 80 | if(nargin==1) 81 | varname=inputname(1); 82 | obj=rootname; 83 | if(isempty(varname)) 84 | varname='root'; 85 | end 86 | rootname=varname; 87 | else 88 | varname=inputname(2); 89 | end 90 | if(length(varargin)==1 && ischar(varargin{1})) 91 | opt=struct('FileName',varargin{1}); 92 | else 93 | opt=varargin2struct(varargin{:}); 94 | end 95 | opt.IsOctave=exist('OCTAVE_VERSION','builtin'); 96 | rootisarray=0; 97 | rootlevel=1; 98 | forceroot=jsonopt('ForceRootName',0,opt); 99 | if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0) 100 | rootisarray=1; 101 | rootlevel=0; 102 | else 103 | if(isempty(rootname)) 104 | rootname=varname; 105 | end 106 | end 107 | if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot) 108 | rootname='root'; 109 | end 110 | json=obj2json(rootname,obj,rootlevel,opt); 111 | if(rootisarray) 112 | json=sprintf('%s\n',json); 113 | else 114 | json=sprintf('{\n%s\n}\n',json); 115 | end 116 | 117 | jsonp=jsonopt('JSONP','',opt); 118 | if(~isempty(jsonp)) 119 | json=sprintf('%s(%s);\n',jsonp,json); 120 | end 121 | 122 | % save to a file if FileName is set, suggested by Patrick Rapin 123 | if(~isempty(jsonopt('FileName','',opt))) 124 | if(jsonopt('SaveBinary',0,opt)==1) 125 | fid = fopen(opt.FileName, 'wb'); 126 | fwrite(fid,json); 127 | else 128 | fid = fopen(opt.FileName, 'wt'); 129 | fwrite(fid,json,'char'); 130 | end 131 | fclose(fid); 132 | end 133 | 134 | %%------------------------------------------------------------------------- 135 | function txt=obj2json(name,item,level,varargin) 136 | 137 | if(iscell(item)) 138 | txt=cell2json(name,item,level,varargin{:}); 139 | elseif(isstruct(item)) 140 | txt=struct2json(name,item,level,varargin{:}); 141 | elseif(ischar(item)) 142 | txt=str2json(name,item,level,varargin{:}); 143 | else 144 | txt=mat2json(name,item,level,varargin{:}); 145 | end 146 | 147 | %%------------------------------------------------------------------------- 148 | function txt=cell2json(name,item,level,varargin) 149 | txt=''; 150 | if(~iscell(item)) 151 | error('input is not a cell'); 152 | end 153 | 154 | dim=size(item); 155 | if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now 156 | item=reshape(item,dim(1),numel(item)/dim(1)); 157 | dim=size(item); 158 | end 159 | len=numel(item); 160 | padding0=repmat(sprintf('\t'),1,level); 161 | padding2=repmat(sprintf('\t'),1,level+1); 162 | if(len>1) 163 | if(~isempty(name)) 164 | txt=sprintf('%s"%s": [\n',padding0, checkname(name,varargin{:})); name=''; 165 | else 166 | txt=sprintf('%s[\n',padding0); 167 | end 168 | elseif(len==0) 169 | if(~isempty(name)) 170 | txt=sprintf('%s"%s": []',padding0, checkname(name,varargin{:})); name=''; 171 | else 172 | txt=sprintf('%s[]',padding0); 173 | end 174 | end 175 | for j=1:dim(2) 176 | if(dim(1)>1) txt=sprintf('%s%s[\n',txt,padding2); end 177 | for i=1:dim(1) 178 | txt=sprintf('%s%s',txt,obj2json(name,item{i,j},level+(dim(1)>1)+1,varargin{:})); 179 | if(i1) txt=sprintf('%s\n%s]',txt,padding2); end 182 | if(j1) txt=sprintf('%s\n%s]',txt,padding0); end 186 | 187 | %%------------------------------------------------------------------------- 188 | function txt=struct2json(name,item,level,varargin) 189 | txt=''; 190 | if(~isstruct(item)) 191 | error('input is not a struct'); 192 | end 193 | dim=size(item); 194 | if(ndims(squeeze(item))>2) % for 3D or higher dimensions, flatten to 2D for now 195 | item=reshape(item,dim(1),numel(item)/dim(1)); 196 | dim=size(item); 197 | end 198 | len=numel(item); 199 | padding0=repmat(sprintf('\t'),1,level); 200 | padding2=repmat(sprintf('\t'),1,level+1); 201 | 202 | if(~isempty(name)) 203 | if(len>1) txt=sprintf('%s"%s": [\n',padding0,checkname(name,varargin{:})); end 204 | else 205 | if(len>1) txt=sprintf('%s[\n',padding0); end 206 | end 207 | for j=1:dim(2) 208 | if(dim(1)>1) txt=sprintf('%s%s[\n',txt,padding2); end 209 | for i=1:dim(1) 210 | names = fieldnames(item(i,j)); 211 | if(~isempty(name) && len==1) 212 | txt=sprintf('%s%s"%s": {\n',txt,repmat(sprintf('\t'),1,level+(dim(1)>1)+(len>1)), checkname(name,varargin{:})); 213 | else 214 | txt=sprintf('%s%s{\n',txt,repmat(sprintf('\t'),1,level+(dim(1)>1)+(len>1))); 215 | end 216 | if(~isempty(names)) 217 | for e=1:length(names) 218 | txt=sprintf('%s%s',txt,obj2json(names{e},getfield(item(i,j),... 219 | names{e}),level+(dim(1)>1)+1+(len>1),varargin{:})); 220 | if(e1)+(len>1))); 225 | if(i1) txt=sprintf('%s\n%s]',txt,padding2); end 228 | if(j1) txt=sprintf('%s\n%s]',txt,padding0); end 231 | 232 | %%------------------------------------------------------------------------- 233 | function txt=str2json(name,item,level,varargin) 234 | txt=''; 235 | if(~ischar(item)) 236 | error('input is not a string'); 237 | end 238 | item=reshape(item, max(size(item),[1 0])); 239 | len=size(item,1); 240 | sep=sprintf(',\n'); 241 | 242 | padding1=repmat(sprintf('\t'),1,level); 243 | padding0=repmat(sprintf('\t'),1,level+1); 244 | 245 | if(~isempty(name)) 246 | if(len>1) txt=sprintf('%s"%s": [\n',padding1,checkname(name,varargin{:})); end 247 | else 248 | if(len>1) txt=sprintf('%s[\n',padding1); end 249 | end 250 | isoct=jsonopt('IsOctave',0,varargin{:}); 251 | for e=1:len 252 | if(isoct) 253 | val=regexprep(item(e,:),'\\','\\'); 254 | val=regexprep(val,'"','\"'); 255 | val=regexprep(val,'^"','\"'); 256 | else 257 | val=regexprep(item(e,:),'\\','\\\\'); 258 | val=regexprep(val,'"','\\"'); 259 | val=regexprep(val,'^"','\\"'); 260 | end 261 | val=escapejsonstring(val); 262 | if(len==1) 263 | obj=['"' checkname(name,varargin{:}) '": ' '"',val,'"']; 264 | if(isempty(name)) obj=['"',val,'"']; end 265 | txt=sprintf('%s%s%s%s',txt,repmat(sprintf('\t'),1,level),obj); 266 | else 267 | txt=sprintf('%s%s%s%s',txt,repmat(sprintf('\t'),1,level+1),['"',val,'"']); 268 | end 269 | if(e==len) sep=''; end 270 | txt=sprintf('%s%s',txt,sep); 271 | end 272 | if(len>1) txt=sprintf('%s\n%s%s',txt,padding1,']'); end 273 | 274 | %%------------------------------------------------------------------------- 275 | function txt=mat2json(name,item,level,varargin) 276 | if(~isnumeric(item) && ~islogical(item)) 277 | error('input is not an array'); 278 | end 279 | 280 | padding1=repmat(sprintf('\t'),1,level); 281 | padding0=repmat(sprintf('\t'),1,level+1); 282 | 283 | if(length(size(item))>2 || issparse(item) || ~isreal(item) || ... 284 | isempty(item) ||jsonopt('ArrayToStruct',0,varargin{:})) 285 | if(isempty(name)) 286 | txt=sprintf('%s{\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... 287 | padding1,padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); 288 | else 289 | txt=sprintf('%s"%s": {\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... 290 | padding1,checkname(name,varargin{:}),padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); 291 | end 292 | else 293 | if(isempty(name)) 294 | txt=sprintf('%s%s',padding1,matdata2json(item,level+1,varargin{:})); 295 | else 296 | if(numel(item)==1 && jsonopt('NoRowBracket',1,varargin{:})==1) 297 | numtxt=regexprep(regexprep(matdata2json(item,level+1,varargin{:}),'^\[',''),']',''); 298 | txt=sprintf('%s"%s": %s',padding1,checkname(name,varargin{:}),numtxt); 299 | else 300 | txt=sprintf('%s"%s": %s',padding1,checkname(name,varargin{:}),matdata2json(item,level+1,varargin{:})); 301 | end 302 | end 303 | return; 304 | end 305 | dataformat='%s%s%s%s%s'; 306 | 307 | if(issparse(item)) 308 | [ix,iy]=find(item); 309 | data=full(item(find(item))); 310 | if(~isreal(item)) 311 | data=[real(data(:)),imag(data(:))]; 312 | if(size(item,1)==1) 313 | % Kludge to have data's 'transposedness' match item's. 314 | % (Necessary for complex row vector handling below.) 315 | data=data'; 316 | end 317 | txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); 318 | end 319 | txt=sprintf(dataformat,txt,padding0,'"_ArrayIsSparse_": ','1', sprintf(',\n')); 320 | if(size(item,1)==1) 321 | % Row vector, store only column indices. 322 | txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... 323 | matdata2json([iy(:),data'],level+2,varargin{:}), sprintf('\n')); 324 | elseif(size(item,2)==1) 325 | % Column vector, store only row indices. 326 | txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... 327 | matdata2json([ix,data],level+2,varargin{:}), sprintf('\n')); 328 | else 329 | % General case, store row and column indices. 330 | txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... 331 | matdata2json([ix,iy,data],level+2,varargin{:}), sprintf('\n')); 332 | end 333 | else 334 | if(isreal(item)) 335 | txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... 336 | matdata2json(item(:)',level+2,varargin{:}), sprintf('\n')); 337 | else 338 | txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); 339 | txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... 340 | matdata2json([real(item(:)) imag(item(:))],level+2,varargin{:}), sprintf('\n')); 341 | end 342 | end 343 | txt=sprintf('%s%s%s',txt,padding1,'}'); 344 | 345 | %%------------------------------------------------------------------------- 346 | function txt=matdata2json(mat,level,varargin) 347 | if(size(mat,1)==1) 348 | pre=''; 349 | post=''; 350 | level=level-1; 351 | else 352 | pre=sprintf('[\n'); 353 | post=sprintf('\n%s]',repmat(sprintf('\t'),1,level-1)); 354 | end 355 | if(isempty(mat)) 356 | txt='null'; 357 | return; 358 | end 359 | floatformat=jsonopt('FloatFormat','%.10g',varargin{:}); 360 | %if(numel(mat)>1) 361 | formatstr=['[' repmat([floatformat ','],1,size(mat,2)-1) [floatformat sprintf('],\n')]]; 362 | %else 363 | % formatstr=[repmat([floatformat ','],1,size(mat,2)-1) [floatformat sprintf(',\n')]]; 364 | %end 365 | 366 | if(nargin>=2 && size(mat,1)>1 && jsonopt('ArrayIndent',1,varargin{:})==1) 367 | formatstr=[repmat(sprintf('\t'),1,level) formatstr]; 368 | end 369 | txt=sprintf(formatstr,mat'); 370 | txt(end-1:end)=[]; 371 | if(islogical(mat) && jsonopt('ParseLogical',0,varargin{:})==1) 372 | txt=regexprep(txt,'1','true'); 373 | txt=regexprep(txt,'0','false'); 374 | end 375 | %txt=regexprep(mat2str(mat),'\s+',','); 376 | %txt=regexprep(txt,';',sprintf('],\n[')); 377 | % if(nargin>=2 && size(mat,1)>1) 378 | % txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']); 379 | % end 380 | txt=[pre txt post]; 381 | if(any(isinf(mat(:)))) 382 | txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',varargin{:})); 383 | end 384 | if(any(isnan(mat(:)))) 385 | txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',varargin{:})); 386 | end 387 | 388 | %%------------------------------------------------------------------------- 389 | function newname=checkname(name,varargin) 390 | isunpack=jsonopt('UnpackHex',1,varargin{:}); 391 | newname=name; 392 | if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once'))) 393 | return 394 | end 395 | if(isunpack) 396 | isoct=jsonopt('IsOctave',0,varargin{:}); 397 | if(~isoct) 398 | newname=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}'); 399 | else 400 | pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start'); 401 | pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end'); 402 | if(isempty(pos)) return; end 403 | str0=name; 404 | pos0=[0 pend(:)' length(name)]; 405 | newname=''; 406 | for i=1:length(pos) 407 | newname=[newname str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))]; 408 | end 409 | if(pos(end)~=length(name)) 410 | newname=[newname str0(pos0(end-1)+1:pos0(end))]; 411 | end 412 | end 413 | end 414 | 415 | %%------------------------------------------------------------------------- 416 | function newstr=escapejsonstring(str) 417 | newstr=str; 418 | isoct=exist('OCTAVE_VERSION','builtin'); 419 | if(isoct) 420 | vv=sscanf(OCTAVE_VERSION,'%f'); 421 | if(vv(1)>=3.8) isoct=0; end 422 | end 423 | if(isoct) 424 | escapechars={'\a','\f','\n','\r','\t','\v'}; 425 | for i=1:length(escapechars); 426 | newstr=regexprep(newstr,escapechars{i},escapechars{i}); 427 | end 428 | else 429 | escapechars={'\a','\b','\f','\n','\r','\t','\v'}; 430 | for i=1:length(escapechars); 431 | newstr=regexprep(newstr,escapechars{i},regexprep(escapechars{i},'\\','\\\\')); 432 | end 433 | end 434 | -------------------------------------------------------------------------------- /private/loadjson.m: -------------------------------------------------------------------------------- 1 | function data = loadjson(fname,varargin) 2 | % 3 | % data=loadjson(fname,opt) 4 | % or 5 | % data=loadjson(fname,'param1',value1,'param2',value2,...) 6 | % 7 | % parse a JSON (JavaScript Object Notation) file or string 8 | % 9 | % authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) 10 | % date: 2011/09/09 11 | % Nedialko Krouchev: http://www.mathworks.com/matlabcentral/fileexchange/25713 12 | % date: 2009/11/02 13 | % François Glineur: http://www.mathworks.com/matlabcentral/fileexchange/23393 14 | % date: 2009/03/22 15 | % Joel Feenstra: 16 | % http://www.mathworks.com/matlabcentral/fileexchange/20565 17 | % date: 2008/07/03 18 | % 19 | % $Id: loadjson.m 437 2014-09-15 18:59:36Z fangq $ 20 | % 21 | % input: 22 | % fname: input file name, if fname contains "{}" or "[]", fname 23 | % will be interpreted as a JSON string 24 | % opt: a struct to store parsing options, opt can be replaced by 25 | % a list of ('param',value) pairs. The param string is equivallent 26 | % to a field in opt. 27 | % 28 | % output: 29 | % dat: a cell array, where {...} blocks are converted into cell arrays, 30 | % and [...] are converted to arrays 31 | % 32 | % license: 33 | % Simplified BSD License 34 | % 35 | % -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) 36 | % 37 | 38 | global pos inStr len esc index_esc len_esc isoct arraytoken 39 | 40 | if(regexp(fname,'[\{\}\]\[]','once')) 41 | string=fname; 42 | elseif(exist(fname,'file')) 43 | fid = fopen(fname,'rb'); 44 | string = fread(fid,inf,'uint8=>char')'; 45 | fclose(fid); 46 | else 47 | error('input file does not exist'); 48 | end 49 | 50 | pos = 1; len = length(string); inStr = string; 51 | isoct=exist('OCTAVE_VERSION','builtin'); 52 | arraytoken=find(inStr=='[' | inStr==']' | inStr=='"'); 53 | jstr=regexprep(inStr,'\\\\',' '); 54 | escquote=regexp(jstr,'\\"'); 55 | arraytoken=sort([arraytoken escquote]); 56 | 57 | % String delimiters and escape chars identified to improve speed: 58 | esc = find(inStr=='"' | inStr=='\' ); % comparable to: regexp(inStr, '["\\]'); 59 | index_esc = 1; len_esc = length(esc); 60 | 61 | opt=varargin2struct(varargin{:}); 62 | jsoncount=1; 63 | while pos <= len 64 | switch(next_char) 65 | case '{' 66 | data{jsoncount} = parse_object(opt); 67 | case '[' 68 | data{jsoncount} = parse_array(opt); 69 | otherwise 70 | error_pos('Outer level structure must be an object or an array'); 71 | end 72 | jsoncount=jsoncount+1; 73 | end % while 74 | 75 | jsoncount=length(data); 76 | if(jsoncount==1 && iscell(data)) 77 | data=data{1}; 78 | end 79 | 80 | if(~isempty(data)) 81 | if(isstruct(data)) % data can be a struct array 82 | data=jstruct2array(data); 83 | elseif(iscell(data)) 84 | data=jcell2array(data); 85 | end 86 | end 87 | 88 | 89 | %% 90 | function newdata=jcell2array(data) 91 | len=length(data); 92 | newdata=data; 93 | for i=1:len 94 | if(isstruct(data{i})) 95 | newdata{i}=jstruct2array(data{i}); 96 | elseif(iscell(data{i})) 97 | newdata{i}=jcell2array(data{i}); 98 | end 99 | end 100 | 101 | %%------------------------------------------------------------------------- 102 | function newdata=jstruct2array(data) 103 | fn=fieldnames(data); 104 | newdata=data; 105 | len=length(data); 106 | for i=1:length(fn) % depth-first 107 | for j=1:len 108 | if(isstruct(getfield(data(j),fn{i}))) 109 | newdata(j)=setfield(newdata(j),fn{i},jstruct2array(getfield(data(j),fn{i}))); 110 | end 111 | end 112 | end 113 | if(~isempty(strmatch('x0x5F_ArrayType_',fn)) && ~isempty(strmatch('x0x5F_ArrayData_',fn))) 114 | newdata=cell(len,1); 115 | for j=1:len 116 | ndata=cast(data(j).x0x5F_ArrayData_,data(j).x0x5F_ArrayType_); 117 | iscpx=0; 118 | if(~isempty(strmatch('x0x5F_ArrayIsComplex_',fn))) 119 | if(data(j).x0x5F_ArrayIsComplex_) 120 | iscpx=1; 121 | end 122 | end 123 | if(~isempty(strmatch('x0x5F_ArrayIsSparse_',fn))) 124 | if(data(j).x0x5F_ArrayIsSparse_) 125 | if(~isempty(strmatch('x0x5F_ArraySize_',fn))) 126 | dim=data(j).x0x5F_ArraySize_; 127 | if(iscpx && size(ndata,2)==4-any(dim==1)) 128 | ndata(:,end-1)=complex(ndata(:,end-1),ndata(:,end)); 129 | end 130 | if isempty(ndata) 131 | % All-zeros sparse 132 | ndata=sparse(dim(1),prod(dim(2:end))); 133 | elseif dim(1)==1 134 | % Sparse row vector 135 | ndata=sparse(1,ndata(:,1),ndata(:,2),dim(1),prod(dim(2:end))); 136 | elseif dim(2)==1 137 | % Sparse column vector 138 | ndata=sparse(ndata(:,1),1,ndata(:,2),dim(1),prod(dim(2:end))); 139 | else 140 | % Generic sparse array. 141 | ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3),dim(1),prod(dim(2:end))); 142 | end 143 | else 144 | if(iscpx && size(ndata,2)==4) 145 | ndata(:,3)=complex(ndata(:,3),ndata(:,4)); 146 | end 147 | ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3)); 148 | end 149 | end 150 | elseif(~isempty(strmatch('x0x5F_ArraySize_',fn))) 151 | if(iscpx && size(ndata,2)==2) 152 | ndata=complex(ndata(:,1),ndata(:,2)); 153 | end 154 | ndata=reshape(ndata(:),data(j).x0x5F_ArraySize_); 155 | end 156 | newdata{j}=ndata; 157 | end 158 | if(len==1) 159 | newdata=newdata{1}; 160 | end 161 | end 162 | 163 | %%------------------------------------------------------------------------- 164 | function object = parse_object(varargin) 165 | parse_char('{'); 166 | object = []; 167 | if next_char ~= '}' 168 | while 1 169 | str = parseStr(varargin{:}); 170 | if isempty(str) 171 | error_pos('Name of value at position %d cannot be empty'); 172 | end 173 | parse_char(':'); 174 | val = parse_value(varargin{:}); 175 | eval( sprintf( 'object.%s = val;', valid_field(str) ) ); 176 | if next_char == '}' 177 | break; 178 | end 179 | parse_char(','); 180 | end 181 | end 182 | parse_char('}'); 183 | 184 | %%------------------------------------------------------------------------- 185 | 186 | function object = parse_array(varargin) % JSON array is written in row-major order 187 | global pos inStr isoct 188 | parse_char('['); 189 | object = cell(0, 1); 190 | dim2=[]; 191 | if next_char ~= ']' 192 | [endpos, e1l, e1r, maxlevel]=matching_bracket(inStr,pos); 193 | arraystr=['[' inStr(pos:endpos)]; 194 | arraystr=regexprep(arraystr,'"_NaN_"','NaN'); 195 | arraystr=regexprep(arraystr,'"([-+]*)_Inf_"','$1Inf'); 196 | arraystr(arraystr==sprintf('\n'))=[]; 197 | arraystr(arraystr==sprintf('\r'))=[]; 198 | %arraystr=regexprep(arraystr,'\s*,',','); % this is slow,sometimes needed 199 | if(~isempty(e1l) && ~isempty(e1r)) % the array is in 2D or higher D 200 | astr=inStr((e1l+1):(e1r-1)); 201 | astr=regexprep(astr,'"_NaN_"','NaN'); 202 | astr=regexprep(astr,'"([-+]*)_Inf_"','$1Inf'); 203 | astr(astr==sprintf('\n'))=[]; 204 | astr(astr==sprintf('\r'))=[]; 205 | astr(astr==' ')=''; 206 | if(isempty(find(astr=='[', 1))) % array is 2D 207 | dim2=length(sscanf(astr,'%f,',[1 inf])); 208 | end 209 | else % array is 1D 210 | astr=arraystr(2:end-1); 211 | astr(astr==' ')=''; 212 | [obj, count, errmsg, nextidx]=sscanf(astr,'%f,',[1,inf]); 213 | if(nextidx>=length(astr)-1) 214 | object=obj; 215 | pos=endpos; 216 | parse_char(']'); 217 | return; 218 | end 219 | end 220 | if(~isempty(dim2)) 221 | astr=arraystr; 222 | astr(astr=='[')=''; 223 | astr(astr==']')=''; 224 | astr(astr==' ')=''; 225 | [obj, count, errmsg, nextidx]=sscanf(astr,'%f,',inf); 226 | if(nextidx>=length(astr)-1) 227 | object=reshape(obj,dim2,numel(obj)/dim2)'; 228 | pos=endpos; 229 | parse_char(']'); 230 | return; 231 | end 232 | end 233 | arraystr=regexprep(arraystr,'\]\s*,','];'); 234 | try 235 | if(isoct && regexp(arraystr,'"','once')) 236 | error('Octave eval can produce empty cells for JSON-like input'); 237 | end 238 | object=eval(arraystr); 239 | pos=endpos; 240 | catch 241 | while 1 242 | val = parse_value(varargin{:}); 243 | object{end+1} = val; 244 | if next_char == ']' 245 | break; 246 | end 247 | parse_char(','); 248 | end 249 | end 250 | end 251 | if(jsonopt('SimplifyCell',0,varargin{:})==1) 252 | try 253 | oldobj=object; 254 | object=cell2mat(object')'; 255 | if(iscell(oldobj) && isstruct(object) && numel(object)>1 && jsonopt('SimplifyCellArray',1,varargin{:})==0) 256 | object=oldobj; 257 | elseif(size(object,1)>1 && ndims(object)==2) 258 | object=object'; 259 | end 260 | catch 261 | end 262 | end 263 | parse_char(']'); 264 | 265 | %%------------------------------------------------------------------------- 266 | 267 | function parse_char(c) 268 | global pos inStr len 269 | skip_whitespace; 270 | if pos > len || inStr(pos) ~= c 271 | error_pos(sprintf('Expected %c at position %%d', c)); 272 | else 273 | pos = pos + 1; 274 | skip_whitespace; 275 | end 276 | 277 | %%------------------------------------------------------------------------- 278 | 279 | function c = next_char 280 | global pos inStr len 281 | skip_whitespace; 282 | if pos > len 283 | c = []; 284 | else 285 | c = inStr(pos); 286 | end 287 | 288 | %%------------------------------------------------------------------------- 289 | 290 | function skip_whitespace 291 | global pos inStr len 292 | while pos <= len && isspace(inStr(pos)) 293 | pos = pos + 1; 294 | end 295 | 296 | %%------------------------------------------------------------------------- 297 | function str = parseStr(varargin) 298 | global pos inStr len esc index_esc len_esc 299 | % len, ns = length(inStr), keyboard 300 | if inStr(pos) ~= '"' 301 | error_pos('String starting with " expected at position %d'); 302 | else 303 | pos = pos + 1; 304 | end 305 | str = ''; 306 | while pos <= len 307 | while index_esc <= len_esc && esc(index_esc) < pos 308 | index_esc = index_esc + 1; 309 | end 310 | if index_esc > len_esc 311 | str = [str inStr(pos:len)]; 312 | pos = len + 1; 313 | break; 314 | else 315 | str = [str inStr(pos:esc(index_esc)-1)]; 316 | pos = esc(index_esc); 317 | end 318 | nstr = length(str); switch inStr(pos) 319 | case '"' 320 | pos = pos + 1; 321 | if(~isempty(str)) 322 | if(strcmp(str,'_Inf_')) 323 | str=Inf; 324 | elseif(strcmp(str,'-_Inf_')) 325 | str=-Inf; 326 | elseif(strcmp(str,'_NaN_')) 327 | str=NaN; 328 | end 329 | end 330 | return; 331 | case '\' 332 | if pos+1 > len 333 | error_pos('End of file reached right after escape character'); 334 | end 335 | pos = pos + 1; 336 | switch inStr(pos) 337 | case {'"' '\' '/'} 338 | str(nstr+1) = inStr(pos); 339 | pos = pos + 1; 340 | case {'b' 'f' 'n' 'r' 't'} 341 | str(nstr+1) = sprintf(['\' inStr(pos)]); 342 | pos = pos + 1; 343 | case 'u' 344 | if pos+4 > len 345 | error_pos('End of file reached in escaped unicode character'); 346 | end 347 | str(nstr+(1:6)) = inStr(pos-1:pos+4); 348 | pos = pos + 5; 349 | end 350 | otherwise % should never happen 351 | str(nstr+1) = inStr(pos), keyboard 352 | pos = pos + 1; 353 | end 354 | end 355 | error_pos('End of file while expecting end of inStr'); 356 | 357 | %%------------------------------------------------------------------------- 358 | 359 | function num = parse_number(varargin) 360 | global pos inStr len isoct 361 | currstr=inStr(pos:end); 362 | numstr=0; 363 | if(isoct~=0) 364 | numstr=regexp(currstr,'^\s*-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?','end'); 365 | [num, one] = sscanf(currstr, '%f', 1); 366 | delta=numstr+1; 367 | else 368 | [num, one, err, delta] = sscanf(currstr, '%f', 1); 369 | if ~isempty(err) 370 | error_pos('Error reading number at position %d'); 371 | end 372 | end 373 | pos = pos + delta-1; 374 | 375 | %%------------------------------------------------------------------------- 376 | 377 | function val = parse_value(varargin) 378 | global pos inStr len 379 | true = 1; false = 0; 380 | 381 | switch(inStr(pos)) 382 | case '"' 383 | val = parseStr(varargin{:}); 384 | return; 385 | case '[' 386 | val = parse_array(varargin{:}); 387 | return; 388 | case '{' 389 | val = parse_object(varargin{:}); 390 | if isstruct(val) 391 | if(~isempty(strmatch('x0x5F_ArrayType_',fieldnames(val), 'exact'))) 392 | val=jstruct2array(val); 393 | end 394 | elseif isempty(val) 395 | val = struct; 396 | end 397 | return; 398 | case {'-','0','1','2','3','4','5','6','7','8','9'} 399 | val = parse_number(varargin{:}); 400 | return; 401 | case 't' 402 | if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'true') 403 | val = true; 404 | pos = pos + 4; 405 | return; 406 | end 407 | case 'f' 408 | if pos+4 <= len && strcmpi(inStr(pos:pos+4), 'false') 409 | val = false; 410 | pos = pos + 5; 411 | return; 412 | end 413 | case 'n' 414 | if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'null') 415 | val = []; 416 | pos = pos + 4; 417 | return; 418 | end 419 | end 420 | error_pos('Value expected at position %d'); 421 | %%------------------------------------------------------------------------- 422 | 423 | function error_pos(msg) 424 | global pos inStr len 425 | poShow = max(min([pos-15 pos-1 pos pos+20],len),1); 426 | if poShow(3) == poShow(2) 427 | poShow(3:4) = poShow(2)+[0 -1]; % display nothing after 428 | end 429 | msg = [sprintf(msg, pos) ': ' ... 430 | inStr(poShow(1):poShow(2)) '' inStr(poShow(3):poShow(4)) ]; 431 | error( ['JSONparser:invalidFormat: ' msg] ); 432 | 433 | %%------------------------------------------------------------------------- 434 | 435 | function str = valid_field(str) 436 | global isoct 437 | % From MATLAB doc: field names must begin with a letter, which may be 438 | % followed by any combination of letters, digits, and underscores. 439 | % Invalid characters will be converted to underscores, and the prefix 440 | % "x0x[Hex code]_" will be added if the first character is not a letter. 441 | pos=regexp(str,'^[^A-Za-z]','once'); 442 | if(~isempty(pos)) 443 | if(~isoct) 444 | str=regexprep(str,'^([^A-Za-z])','x0x${sprintf(''%X'',unicode2native($1))}_','once'); 445 | else 446 | str=sprintf('x0x%X_%s',char(str(1)),str(2:end)); 447 | end 448 | end 449 | if(isempty(regexp(str,'[^0-9A-Za-z_]', 'once' ))) return; end 450 | if(~isoct) 451 | str=regexprep(str,'([^0-9A-Za-z_])','_0x${sprintf(''%X'',unicode2native($1))}_'); 452 | else 453 | pos=regexp(str,'[^0-9A-Za-z_]'); 454 | if(isempty(pos)) return; end 455 | str0=str; 456 | pos0=[0 pos(:)' length(str)]; 457 | str=''; 458 | for i=1:length(pos) 459 | str=[str str0(pos0(i)+1:pos(i)-1) sprintf('_0x%X_',str0(pos(i)))]; 460 | end 461 | if(pos(end)~=length(str)) 462 | str=[str str0(pos0(end-1)+1:pos0(end))]; 463 | end 464 | end 465 | %str(~isletter(str) & ~('0' <= str & str <= '9')) = '_'; 466 | 467 | %%------------------------------------------------------------------------- 468 | function endpos = matching_quote(str,pos) 469 | len=length(str); 470 | while(pos1 && str(pos-1)=='\')) 473 | endpos=pos; 474 | return; 475 | end 476 | end 477 | pos=pos+1; 478 | end 479 | error('unmatched quotation mark'); 480 | %%------------------------------------------------------------------------- 481 | function [endpos, e1l, e1r, maxlevel] = matching_bracket(str,pos) 482 | global arraytoken 483 | level=1; 484 | maxlevel=level; 485 | endpos=0; 486 | bpos=arraytoken(arraytoken>=pos); 487 | tokens=str(bpos); 488 | len=length(tokens); 489 | pos=1; 490 | e1l=[]; 491 | e1r=[]; 492 | while(pos<=len) 493 | c=tokens(pos); 494 | if(c==']') 495 | level=level-1; 496 | if(isempty(e1r)) e1r=bpos(pos); end 497 | if(level==0) 498 | endpos=bpos(pos); 499 | return 500 | end 501 | end 502 | if(c=='[') 503 | if(isempty(e1l)) e1l=bpos(pos); end 504 | level=level+1; 505 | maxlevel=max(maxlevel,level); 506 | end 507 | if(c=='"') 508 | pos=matching_quote(tokens,pos+1); 509 | end 510 | pos=pos+1; 511 | end 512 | if(endpos==0) 513 | error('unmatched "]"'); 514 | end 515 | 516 | --------------------------------------------------------------------------------