]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_json_tools.c
d8e0a8fcc2898443793578f15103df956d34dbe0
[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 <opensrf/osrf_json.h>
17 #include <opensrf/osrf_json_utils.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         The calling code is responsible for freeing the formatted copy.
49 */
50 char* jsonFormatString( const char* string ) {
51         if(!string) return strdup("");
52
53         growing_buffer* buf = buffer_init(64);
54         int i;
55         int depth = 0;
56
57         char c;
58         for(i = 0; string[i]; i++) {
59                 c = string[i];
60
61                 if( c == '{' || c == '[' ) {
62
63                         OSRF_BUFFER_ADD_CHAR( buf, c );
64                         OSRF_BUFFER_ADD_CHAR( buf, '\n' );
65                         append_indentation( buf, ++depth );
66
67                 } else if( c == '}' || c == ']' ) {
68
69                         OSRF_BUFFER_ADD_CHAR( buf, '\n' );
70                         append_indentation( buf, --depth );
71                         OSRF_BUFFER_ADD_CHAR( buf, c );
72
73                 } else if( c == ',' ) {
74
75                         OSRF_BUFFER_ADD_CHAR( buf, ',' );
76                         OSRF_BUFFER_ADD_CHAR( buf, '\n' );
77                         append_indentation( buf, depth );
78
79                 } else
80                         OSRF_BUFFER_ADD_CHAR(buf, c);
81         }
82
83     return buffer_release(buf);
84 }
85
86
87
88 jsonObject* jsonObjectDecodeClass( const jsonObject* obj ) {
89         if(!obj) return jsonNewObject(NULL);
90
91         jsonObject* newObj                       = NULL; 
92         const jsonObject* classObj       = NULL;
93         const jsonObject* payloadObj = NULL;
94         int i;
95
96         if( obj->type == JSON_HASH ) {
97
98                 /* are we a special class object? */
99                 if( (classObj = jsonObjectGetKeyConst( obj, JSON_CLASS_KEY )) ) {
100
101                         /* do we have a payload */
102                         if( (payloadObj = jsonObjectGetKeyConst( obj, JSON_DATA_KEY )) ) {
103                                 newObj = jsonObjectDecodeClass( payloadObj );
104                                 jsonObjectSetClass( newObj, jsonObjectGetString(classObj) );
105
106                         } else { /* class is defined but there is no payload */
107                                 return NULL;
108                         }
109
110                 } else { /* we're a regular hash */
111
112                         jsonIterator* itr = jsonNewIterator(obj);
113                         jsonObject* tmp;
114                         newObj = jsonNewObjectType(JSON_HASH);
115                         while( (tmp = jsonIteratorNext(itr)) ) {
116                                 jsonObject* o = jsonObjectDecodeClass(tmp);
117                                 jsonObjectSetKey( newObj, itr->key, o );
118                         }
119                         jsonIteratorFree(itr);
120                         if( obj->classname )
121                                 jsonObjectSetClass( newObj, obj->classname );
122                 }
123
124         } else {
125
126                 if( obj->type == JSON_ARRAY ) { /* we're an array */
127                         newObj = jsonNewObjectType(JSON_ARRAY);
128                         for( i = 0; i != obj->size; i++ ) {
129                                 jsonObject* tmp = jsonObjectDecodeClass(jsonObjectGetIndex( obj, i ) );
130                                 jsonObjectSetIndex( newObj, i, tmp );
131                         }
132                         if( obj->classname )
133                                 jsonObjectSetClass( newObj, obj->classname );
134
135                 } else { /* not an aggregate type */
136                         newObj = jsonObjectClone(obj);
137                 }
138         }
139                 
140         return newObj;
141 }
142
143 jsonObject* jsonObjectEncodeClass( const jsonObject* obj ) {
144         return _jsonObjectEncodeClass( obj, 0 );
145 }
146
147 static jsonObject* _jsonObjectEncodeClass( const jsonObject* obj, int ignoreClass ) {
148
149         //if(!obj) return NULL;
150         if(!obj) return jsonNewObject(NULL);
151         jsonObject* newObj = NULL;
152
153         if( obj->classname && ! ignoreClass ) {
154                 newObj = jsonNewObjectType(JSON_HASH);
155
156                 jsonObjectSetKey( newObj, 
157                         JSON_CLASS_KEY, jsonNewObject(obj->classname) ); 
158
159                 jsonObjectSetKey( newObj, 
160                         JSON_DATA_KEY, _jsonObjectEncodeClass(obj, 1));
161
162         } else if( obj->type == JSON_HASH ) {
163
164                 jsonIterator* itr = jsonNewIterator(obj);
165                 jsonObject* tmp;
166                 newObj = jsonNewObjectType(JSON_HASH);
167
168                 while( (tmp = jsonIteratorNext(itr)) ) {
169                         jsonObjectSetKey( newObj, itr->key, 
170                                         _jsonObjectEncodeClass(tmp, 0));
171                 }
172                 jsonIteratorFree(itr);
173
174         } else if( obj->type == JSON_ARRAY ) {
175
176                 newObj = jsonNewObjectType(JSON_ARRAY);
177                 int i;
178                 for( i = 0; i != obj->size; i++ ) {
179                         jsonObjectSetIndex( newObj, i, 
180                                 _jsonObjectEncodeClass(jsonObjectGetIndex( obj, i ), 0 ));
181                 }
182
183         } else {
184                 newObj = jsonObjectClone(obj);
185         }
186
187         return newObj;
188 }
189
190 jsonObject* jsonObjectFindPath( const jsonObject* obj, const char* format, ...) {
191         if(!obj || !format || strlen(format) < 1) return NULL;  
192
193         VA_LIST_TO_STRING(format);
194         char* buf = VA_BUF;
195         char* token = NULL;
196         char* tt; /* strtok storage */
197
198         /* special case where path starts with //  (start anywhere) */
199         if(buf[0] == '/' && buf[1] == '/' && buf[2] != '\0') {
200
201                 /* copy the path before strtok_r destroys it */
202                 char* pathcopy = strdup(buf);
203
204                 /* grab the root of the path */
205                 token = strtok_r(buf, "/", &tt);
206                 if(!token) {
207                         free(pathcopy);
208                         return NULL;
209                 }
210
211                 jsonObject* it = findMultiPath(obj, token, pathcopy + 1);
212                 free(pathcopy);
213                 return it;
214         }
215         else
216         {
217                 /* grab the root of the path */
218                 token = strtok_r(buf, "/", &tt);
219                 if(!token) return NULL;
220
221                 do {
222                         obj = jsonObjectGetKeyConst(obj, token);
223                 } while( (token = strtok_r(NULL, "/", &tt)) && obj);
224
225                 return jsonObjectClone(obj);
226         }
227 }
228
229 /* --------------------------------------------------------------- */
230
231 /* Utility method. finds any object in the tree that matches the path.  
232         Use this for finding paths that start with '//' */
233 static jsonObject* findMultiPath(const jsonObject* obj,
234                 const char* root, const char* path) {
235
236         if(!obj || ! root || !path) return NULL;
237
238         /* collect all of the potential objects */
239         jsonObject* arr = findMultiPathRecurse(obj, root);
240
241         /* path is just /root or /root/ */
242         if( strlen(root) + 2 >= strlen(path) ) {
243                 return arr;
244
245         } else {
246
247                 /* container for fully matching objects */
248                 jsonObject* newarr = jsonNewObjectType(JSON_ARRAY);
249                 int i;
250
251                 /* gather all of the sub-objects that match the full path */
252                 for( i = 0; i < arr->size; i++ ) {
253                         const jsonObject* a = jsonObjectGetIndex(arr, i);
254                         jsonObject* thing = jsonObjectFindPath(a , path + strlen(root) + 1); 
255
256                         if(thing) { //jsonObjectPush(newarr, thing);
257                                 if(thing->type == JSON_ARRAY) {
258                         int i;
259                                         for( i = 0; i != thing->size; i++ )
260                                                 jsonObjectPush(newarr, jsonObjectClone(jsonObjectGetIndex(thing,i)));
261                                         jsonObjectFree(thing);
262                                 } else {
263                                         jsonObjectPush(newarr, thing);
264                                 }
265                         }
266                 }
267
268                 jsonObjectFree(arr);
269                 return newarr;
270         }
271 }
272
273 /* returns a list of object whose key is 'root'.  These are used as
274         potential objects when doing a // search */
275 static jsonObject* findMultiPathRecurse(const jsonObject* obj, const char* root) {
276
277         jsonObject* arr = jsonNewObjectType(JSON_ARRAY);
278         if(!obj) return arr;
279
280         int i;
281
282         /* if the current object has a node that matches, add it */
283
284         const jsonObject* o = jsonObjectGetKeyConst(obj, root);
285         if(o) jsonObjectPush( arr, jsonObjectClone(o) );
286
287         jsonObject* tmp = NULL;
288         jsonObject* childarr;
289         jsonIterator* itr = jsonNewIterator(obj);
290
291         /* recurse through the children and find all potential nodes */
292         while( (tmp = jsonIteratorNext(itr)) ) {
293                 childarr = findMultiPathRecurse(tmp, root);
294                 if(childarr && childarr->size > 0) {
295                         for( i = 0; i!= childarr->size; i++ ) {
296                                 jsonObjectPush( arr, jsonObjectClone(jsonObjectGetIndex(childarr, i)) );
297                         }
298                 }
299                 jsonObjectFree(childarr);
300         }
301
302         jsonIteratorFree(itr);
303
304         return arr;
305 }