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