2 Copyright (C) 2006 Georgia Public Library Service
3 Bill Erickson <billserickson@gmail.com>
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.
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.
17 #include "opensrf/osrf_json.h"
18 #include "opensrf/osrf_json_utils.h"
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 );
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.
30 We append 2 spaces per degree of indentation.
32 static void append_indentation( growing_buffer* buf, int depth ) {
35 memset( indent, ' ', n );
36 buffer_add_n( buf, indent, n );
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.
44 Create a copy of the input JSON string, with newlines and
45 indentation for readability.
47 If the input pointer is NULL, return an empty string.
49 WARNING: if the input JSON is not well-formed, the output JSON is likely
50 to be even worse-formed.
52 The calling code is responsible for freeing the formatted copy.
54 char* jsonFormatString( const char* string ) {
55 if( !string ) return strdup( "" );
57 growing_buffer* buf = buffer_init( 64 );
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
65 for( i = 0; string[i]; i++ ) {
68 if( c == '{' || c == '[' ) {
70 OSRF_BUFFER_ADD_CHAR( buf, c );
72 OSRF_BUFFER_ADD_CHAR( buf, '\n' );
73 append_indentation( buf, ++depth );
77 } else if( c == '}' || c == ']' ) {
80 OSRF_BUFFER_ADD_CHAR( buf, '\n' );
81 append_indentation( buf, --depth );
84 OSRF_BUFFER_ADD_CHAR( buf, c );
86 } else if( c == ',' ) {
88 OSRF_BUFFER_ADD_CHAR( buf, ',' );
90 OSRF_BUFFER_ADD_CHAR( buf, '\n' );
91 append_indentation( buf, depth );
96 // Ignore white space at the beginning of a line
98 if( !isspace( (unsigned char) c )) {
99 OSRF_BUFFER_ADD_CHAR( buf, c );
103 OSRF_BUFFER_ADD_CHAR( buf, c );
110 if( '\"' == c && !escaped )
111 in_quote = !in_quote;
116 return buffer_release( buf );
121 jsonObject* jsonObjectDecodeClass( const jsonObject* obj ) {
122 if(!obj) return jsonNewObject(NULL);
124 jsonObject* newObj = NULL;
125 const jsonObject* classObj = NULL;
126 const jsonObject* payloadObj = NULL;
129 if( obj->type == JSON_HASH ) {
131 /* are we a special class object? */
132 if( (classObj = jsonObjectGetKeyConst( obj, JSON_CLASS_KEY )) ) {
134 /* do we have a payload */
135 if( (payloadObj = jsonObjectGetKeyConst( obj, JSON_DATA_KEY )) ) {
136 newObj = jsonObjectDecodeClass( payloadObj );
137 jsonObjectSetClass( newObj, jsonObjectGetString(classObj) );
139 } else { /* class is defined but there is no payload */
143 } else { /* we're a regular hash */
145 jsonIterator* itr = jsonNewIterator(obj);
147 newObj = jsonNewObjectType(JSON_HASH);
148 while( (tmp = jsonIteratorNext(itr)) ) {
149 jsonObject* o = jsonObjectDecodeClass(tmp);
150 jsonObjectSetKey( newObj, itr->key, o );
152 jsonIteratorFree(itr);
154 jsonObjectSetClass( newObj, obj->classname );
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 );
166 jsonObjectSetClass( newObj, obj->classname );
168 } else { /* not an aggregate type */
169 newObj = jsonObjectClone(obj);
176 jsonObject* jsonObjectEncodeClass( const jsonObject* obj ) {
177 return _jsonObjectEncodeClass( obj, 0 );
180 static jsonObject* _jsonObjectEncodeClass( const jsonObject* obj, int ignoreClass ) {
182 //if(!obj) return NULL;
183 if(!obj) return jsonNewObject(NULL);
184 jsonObject* newObj = NULL;
186 if( obj->classname && ! ignoreClass ) {
187 newObj = jsonNewObjectType(JSON_HASH);
189 jsonObjectSetKey( newObj,
190 JSON_CLASS_KEY, jsonNewObject(obj->classname) );
192 jsonObjectSetKey( newObj,
193 JSON_DATA_KEY, _jsonObjectEncodeClass(obj, 1));
195 } else if( obj->type == JSON_HASH ) {
197 jsonIterator* itr = jsonNewIterator(obj);
199 newObj = jsonNewObjectType(JSON_HASH);
201 while( (tmp = jsonIteratorNext(itr)) ) {
202 jsonObjectSetKey( newObj, itr->key,
203 _jsonObjectEncodeClass(tmp, 0));
205 jsonIteratorFree(itr);
207 } else if( obj->type == JSON_ARRAY ) {
209 newObj = jsonNewObjectType(JSON_ARRAY);
211 for( i = 0; i != obj->size; i++ ) {
212 jsonObjectSetIndex( newObj, i,
213 _jsonObjectEncodeClass(jsonObjectGetIndex( obj, i ), 0 ));
217 newObj = jsonObjectClone(obj);
223 jsonObject* jsonObjectFindPath( const jsonObject* obj, const char* format, ...) {
224 if(!obj || !format || strlen(format) < 1) return NULL;
226 VA_LIST_TO_STRING(format);
229 char* tt; /* strtok storage */
231 /* special case where path starts with // (start anywhere) */
232 if(buf[0] == '/' && buf[1] == '/' && buf[2] != '\0') {
234 /* copy the path before strtok_r destroys it */
235 char* pathcopy = strdup(buf);
237 /* grab the root of the path */
238 token = strtok_r(buf, "/", &tt);
244 jsonObject* it = findMultiPath(obj, token, pathcopy + 1);
250 /* grab the root of the path */
251 token = strtok_r(buf, "/", &tt);
252 if(!token) return NULL;
255 obj = jsonObjectGetKeyConst(obj, token);
256 } while( (token = strtok_r(NULL, "/", &tt)) && obj);
258 return jsonObjectClone(obj);
262 /* --------------------------------------------------------------- */
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) {
269 if(!obj || ! root || !path) return NULL;
271 /* collect all of the potential objects */
272 jsonObject* arr = findMultiPathRecurse(obj, root);
274 /* path is just /root or /root/ */
275 if( strlen(root) + 2 >= strlen(path) ) {
280 /* container for fully matching objects */
281 jsonObject* newarr = jsonNewObjectType(JSON_ARRAY);
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);
289 if(thing) { //jsonObjectPush(newarr, thing);
290 if(thing->type == JSON_ARRAY) {
292 for( i = 0; i != thing->size; i++ )
293 jsonObjectPush(newarr, jsonObjectClone(jsonObjectGetIndex(thing,i)));
294 jsonObjectFree(thing);
296 jsonObjectPush(newarr, thing);
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) {
310 jsonObject* arr = jsonNewObjectType(JSON_ARRAY);
315 /* if the current object has a node that matches, add it */
317 const jsonObject* o = jsonObjectGetKeyConst(obj, root);
318 if(o) jsonObjectPush( arr, jsonObjectClone(o) );
320 jsonObject* tmp = NULL;
321 jsonObject* childarr;
322 jsonIterator* itr = jsonNewIterator(obj);
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)) );
332 jsonObjectFree(childarr);
335 jsonIteratorFree(itr);