]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_json_tools.c
e444db9dc3c04c618934ab5389b0054950cfb92c
[OpenSRF.git] / src / libopensrf / osrf_json_tools.c
1 /*
2 Copyright (C) 2006  Georgia Public Library Service 
3 Bill Erickson <billserickson@gmail.com>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 */
15
16 #include <ctype.h>
17 #include "opensrf/osrf_json.h"
18 #include "opensrf/osrf_json_utils.h"
19
20 static jsonObject* findMultiPath( const jsonObject* o,
21                 const char* root, const char* path );
22 static jsonObject* findMultiPathRecurse( const jsonObject* o, const char* root );
23 static jsonObject* _jsonObjectEncodeClass( const jsonObject* obj, int ignoreClass );
24
25 /**
26         @brief Append some spaces to a growing_buffer, for indentation.
27         @param buf Pointer to the growing_buffer.
28         @param depth Degree of indentation.
29
30         We append 2 spaces per degree of indentation.
31 */
32 static void append_indentation( growing_buffer* buf, int depth ) {
33         size_t n = 2 * depth;
34         char indent[ n ];
35         memset( indent, ' ', n );
36         buffer_add_n( buf, indent, n );
37 }
38
39 /**
40         @brief Make a prettyprint copy of a JSON string.
41         @param string Pointer to the JSON string to be formatted.
42         @return Pointer to a newly allocated and reformatted JSON string.
43
44         Create a copy of the input JSON string, with newlines and
45         indentation for readability.
46
47         If the input pointer is NULL, return an empty string.
48
49         WARNING: if the input JSON is not well-formed, the output JSON is likely
50         to be even worse-formed.
51
52         The calling code is responsible for freeing the formatted copy.
53 */
54 char* jsonFormatString( const char* string ) {
55         if( !string ) return strdup( "" );
56
57         growing_buffer* buf = buffer_init( 64 );
58         int i;
59         int depth = 0;
60         int in_quote = 0;   // boolean; true if in a string literal
61         int escaped = 0;    // boolean: true if previous character was a backslash
62         int beginning = 1;  // boolean: true if we're starting a new line
63
64         char c;
65         for( i = 0; string[i]; i++ ) {
66                 c = string[ i ];
67
68                 if( c == '{' || c == '[' ) {
69
70                         OSRF_BUFFER_ADD_CHAR( buf, c );
71                         if( !in_quote ) {
72                                 OSRF_BUFFER_ADD_CHAR( buf, '\n' );
73                                 append_indentation( buf, ++depth );
74                                 beginning = 1;
75                         }
76
77                 } else if( c == '}' || c == ']' ) {
78
79                         if( !in_quote ) {
80                                 OSRF_BUFFER_ADD_CHAR( buf, '\n' );
81                                 append_indentation( buf, --depth );
82                                 beginning = 1;
83                         }
84                         OSRF_BUFFER_ADD_CHAR( buf, c );
85
86                 } else if( c == ',' ) {
87
88                         OSRF_BUFFER_ADD_CHAR( buf, ',' );
89                         if( !in_quote ) {
90                                 OSRF_BUFFER_ADD_CHAR( buf, '\n' );
91                                 append_indentation( buf, depth );
92                                 beginning = 1;
93                         }
94
95                 } else {
96                         // Ignore white space at the beginning of a line
97                         if( beginning ) {
98                                 if( !isspace( (unsigned char) c )) {
99                                         OSRF_BUFFER_ADD_CHAR( buf, c );
100                                         beginning = 0;
101                                 }
102                         } else {
103                                 OSRF_BUFFER_ADD_CHAR( buf, c );
104                         }
105                 }
106
107                 if( '\\' == c )
108                         escaped = !escaped;
109                 else {
110                         if( '\"' == c && !escaped )
111                                 in_quote = !in_quote;
112                         escaped = 0;
113                 }
114         }
115
116     return buffer_release( buf );
117 }
118
119
120
121 jsonObject* jsonObjectDecodeClass( const jsonObject* obj ) {
122         if(!obj) return jsonNewObject(NULL);
123
124         jsonObject* newObj                       = NULL; 
125         const jsonObject* classObj       = NULL;
126         const jsonObject* payloadObj = NULL;
127         int i;
128
129         if( obj->type == JSON_HASH ) {
130
131                 /* are we a special class object? */
132                 if( (classObj = jsonObjectGetKeyConst( obj, JSON_CLASS_KEY )) ) {
133
134                         /* do we have a payload */
135                         if( (payloadObj = jsonObjectGetKeyConst( obj, JSON_DATA_KEY )) ) {
136                                 newObj = jsonObjectDecodeClass( payloadObj );
137                                 jsonObjectSetClass( newObj, jsonObjectGetString(classObj) );
138
139                         } else { /* class is defined but there is no payload */
140                                 return NULL;
141                         }
142
143                 } else { /* we're a regular hash */
144
145                         jsonIterator* itr = jsonNewIterator(obj);
146                         jsonObject* tmp;
147                         newObj = jsonNewObjectType(JSON_HASH);
148                         while( (tmp = jsonIteratorNext(itr)) ) {
149                                 jsonObject* o = jsonObjectDecodeClass(tmp);
150                                 jsonObjectSetKey( newObj, itr->key, o );
151                         }
152                         jsonIteratorFree(itr);
153                         if( obj->classname )
154                                 jsonObjectSetClass( newObj, obj->classname );
155                 }
156
157         } else {
158
159                 if( obj->type == JSON_ARRAY ) { /* we're an array */
160                         newObj = jsonNewObjectType(JSON_ARRAY);
161                         for( i = 0; i != obj->size; i++ ) {
162                                 jsonObject* tmp = jsonObjectDecodeClass(jsonObjectGetIndex( obj, i ) );
163                                 jsonObjectSetIndex( newObj, i, tmp );
164                         }
165                         if( obj->classname )
166                                 jsonObjectSetClass( newObj, obj->classname );
167
168                 } else { /* not an aggregate type */
169                         newObj = jsonObjectClone(obj);
170                 }
171         }
172                 
173         return newObj;
174 }
175
176 jsonObject* jsonObjectEncodeClass( const jsonObject* obj ) {
177         return _jsonObjectEncodeClass( obj, 0 );
178 }
179
180 static jsonObject* _jsonObjectEncodeClass( const jsonObject* obj, int ignoreClass ) {
181
182         //if(!obj) return NULL;
183         if(!obj) return jsonNewObject(NULL);
184         jsonObject* newObj = NULL;
185
186         if( obj->classname && ! ignoreClass ) {
187                 newObj = jsonNewObjectType(JSON_HASH);
188
189                 jsonObjectSetKey( newObj, 
190                         JSON_CLASS_KEY, jsonNewObject(obj->classname) ); 
191
192                 jsonObjectSetKey( newObj, 
193                         JSON_DATA_KEY, _jsonObjectEncodeClass(obj, 1));
194
195         } else if( obj->type == JSON_HASH ) {
196
197                 jsonIterator* itr = jsonNewIterator(obj);
198                 jsonObject* tmp;
199                 newObj = jsonNewObjectType(JSON_HASH);
200
201                 while( (tmp = jsonIteratorNext(itr)) ) {
202                         jsonObjectSetKey( newObj, itr->key, 
203                                         _jsonObjectEncodeClass(tmp, 0));
204                 }
205                 jsonIteratorFree(itr);
206
207         } else if( obj->type == JSON_ARRAY ) {
208
209                 newObj = jsonNewObjectType(JSON_ARRAY);
210                 int i;
211                 for( i = 0; i != obj->size; i++ ) {
212                         jsonObjectSetIndex( newObj, i, 
213                                 _jsonObjectEncodeClass(jsonObjectGetIndex( obj, i ), 0 ));
214                 }
215
216         } else {
217                 newObj = jsonObjectClone(obj);
218         }
219
220         return newObj;
221 }
222
223 jsonObject* jsonObjectFindPath( const jsonObject* obj, const char* format, ...) {
224         if(!obj || !format || strlen(format) < 1) return NULL;  
225
226         VA_LIST_TO_STRING(format);
227         char* buf = VA_BUF;
228         char* token = NULL;
229         char* tt; /* strtok storage */
230
231         /* special case where path starts with //  (start anywhere) */
232         if(buf[0] == '/' && buf[1] == '/' && buf[2] != '\0') {
233
234                 /* copy the path before strtok_r destroys it */
235                 char* pathcopy = strdup(buf);
236
237                 /* grab the root of the path */
238                 token = strtok_r(buf, "/", &tt);
239                 if(!token) {
240                         free(pathcopy);
241                         return NULL;
242                 }
243
244                 jsonObject* it = findMultiPath(obj, token, pathcopy + 1);
245                 free(pathcopy);
246                 return it;
247         }
248         else
249         {
250                 /* grab the root of the path */
251                 token = strtok_r(buf, "/", &tt);
252                 if(!token) return NULL;
253
254                 do {
255                         obj = jsonObjectGetKeyConst(obj, token);
256                 } while( (token = strtok_r(NULL, "/", &tt)) && obj);
257
258                 return jsonObjectClone(obj);
259         }
260 }
261
262 /* --------------------------------------------------------------- */
263
264 /* Utility method. finds any object in the tree that matches the path.  
265         Use this for finding paths that start with '//' */
266 static jsonObject* findMultiPath(const jsonObject* obj,
267                 const char* root, const char* path) {
268
269         if(!obj || ! root || !path) return NULL;
270
271         /* collect all of the potential objects */
272         jsonObject* arr = findMultiPathRecurse(obj, root);
273
274         /* path is just /root or /root/ */
275         if( strlen(root) + 2 >= strlen(path) ) {
276                 return arr;
277
278         } else {
279
280                 /* container for fully matching objects */
281                 jsonObject* newarr = jsonNewObjectType(JSON_ARRAY);
282                 int i;
283
284                 /* gather all of the sub-objects that match the full path */
285                 for( i = 0; i < arr->size; i++ ) {
286                         const jsonObject* a = jsonObjectGetIndex(arr, i);
287                         jsonObject* thing = jsonObjectFindPath(a , path + strlen(root) + 1); 
288
289                         if(thing) { //jsonObjectPush(newarr, thing);
290                                 if(thing->type == JSON_ARRAY) {
291                         int i;
292                                         for( i = 0; i != thing->size; i++ )
293                                                 jsonObjectPush(newarr, jsonObjectClone(jsonObjectGetIndex(thing,i)));
294                                         jsonObjectFree(thing);
295                                 } else {
296                                         jsonObjectPush(newarr, thing);
297                                 }
298                         }
299                 }
300
301                 jsonObjectFree(arr);
302                 return newarr;
303         }
304 }
305
306 /* returns a list of object whose key is 'root'.  These are used as
307         potential objects when doing a // search */
308 static jsonObject* findMultiPathRecurse(const jsonObject* obj, const char* root) {
309
310         jsonObject* arr = jsonNewObjectType(JSON_ARRAY);
311         if(!obj) return arr;
312
313         int i;
314
315         /* if the current object has a node that matches, add it */
316
317         const jsonObject* o = jsonObjectGetKeyConst(obj, root);
318         if(o) jsonObjectPush( arr, jsonObjectClone(o) );
319
320         jsonObject* tmp = NULL;
321         jsonObject* childarr;
322         jsonIterator* itr = jsonNewIterator(obj);
323
324         /* recurse through the children and find all potential nodes */
325         while( (tmp = jsonIteratorNext(itr)) ) {
326                 childarr = findMultiPathRecurse(tmp, root);
327                 if(childarr && childarr->size > 0) {
328                         for( i = 0; i!= childarr->size; i++ ) {
329                                 jsonObjectPush( arr, jsonObjectClone(jsonObjectGetIndex(childarr, i)) );
330                         }
331                 }
332                 jsonObjectFree(childarr);
333         }
334
335         jsonIteratorFree(itr);
336
337         return arr;
338 }