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"
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 );
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.
29 We append 2 spaces per degree of indentation.
31 static void append_indentation( growing_buffer* buf, int depth ) {
34 memset( indent, ' ', n );
35 buffer_add_n( buf, indent, n );
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.
43 Create a copy of the input JSON string, with newlines and
44 indentation for readability.
46 If the input pointer is NULL, return an empty string.
48 WARNING: if the input JSON is not well-formed, the output JSON is likely
49 to be even worse-formed.
51 The calling code is responsible for freeing the formatted copy.
53 char* jsonFormatString( const char* string ) {
54 if( !string ) return strdup( "" );
56 growing_buffer* buf = buffer_init( 64 );
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
64 for( i = 0; string[i]; i++ ) {
67 if( c == '{' || c == '[' ) {
69 OSRF_BUFFER_ADD_CHAR( buf, c );
71 OSRF_BUFFER_ADD_CHAR( buf, '\n' );
72 append_indentation( buf, ++depth );
76 } else if( c == '}' || c == ']' ) {
79 OSRF_BUFFER_ADD_CHAR( buf, '\n' );
80 append_indentation( buf, --depth );
83 OSRF_BUFFER_ADD_CHAR( buf, c );
85 } else if( c == ',' ) {
87 OSRF_BUFFER_ADD_CHAR( buf, ',' );
89 OSRF_BUFFER_ADD_CHAR( buf, '\n' );
90 append_indentation( buf, depth );
95 // Ignore white space at the beginning of a line
97 if( !isspace( (unsigned char) c )) {
98 OSRF_BUFFER_ADD_CHAR( buf, c );
102 OSRF_BUFFER_ADD_CHAR( buf, c );
109 if( '\"' == c && !escaped )
110 in_quote = !in_quote;
115 return buffer_release( buf );
120 jsonObject* jsonObjectDecodeClass( const jsonObject* obj ) {
121 if(!obj) return jsonNewObject(NULL);
123 jsonObject* newObj = NULL;
124 const jsonObject* classObj = NULL;
125 const jsonObject* payloadObj = NULL;
128 if( obj->type == JSON_HASH ) {
130 /* are we a special class object? */
131 if( (classObj = jsonObjectGetKeyConst( obj, JSON_CLASS_KEY )) ) {
133 /* do we have a payload */
134 if( (payloadObj = jsonObjectGetKeyConst( obj, JSON_DATA_KEY )) ) {
135 newObj = jsonObjectDecodeClass( payloadObj );
136 jsonObjectSetClass( newObj, jsonObjectGetString(classObj) );
138 } else { /* class is defined but there is no payload */
142 } else { /* we're a regular hash */
144 jsonIterator* itr = jsonNewIterator(obj);
146 newObj = jsonNewObjectType(JSON_HASH);
147 while( (tmp = jsonIteratorNext(itr)) ) {
148 jsonObject* o = jsonObjectDecodeClass(tmp);
149 jsonObjectSetKey( newObj, itr->key, o );
151 jsonIteratorFree(itr);
153 jsonObjectSetClass( newObj, obj->classname );
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 );
165 jsonObjectSetClass( newObj, obj->classname );
167 } else { /* not an aggregate type */
168 newObj = jsonObjectClone(obj);
175 jsonObject* jsonObjectEncodeClass( const jsonObject* obj ) {
176 return _jsonObjectEncodeClass( obj, 0 );
179 static jsonObject* _jsonObjectEncodeClass( const jsonObject* obj, int ignoreClass ) {
181 //if(!obj) return NULL;
182 if(!obj) return jsonNewObject(NULL);
183 jsonObject* newObj = NULL;
185 if( obj->classname && ! ignoreClass ) {
186 newObj = jsonNewObjectType(JSON_HASH);
188 jsonObjectSetKey( newObj,
189 JSON_CLASS_KEY, jsonNewObject(obj->classname) );
191 jsonObjectSetKey( newObj,
192 JSON_DATA_KEY, _jsonObjectEncodeClass(obj, 1));
194 } else if( obj->type == JSON_HASH ) {
196 jsonIterator* itr = jsonNewIterator(obj);
198 newObj = jsonNewObjectType(JSON_HASH);
200 while( (tmp = jsonIteratorNext(itr)) ) {
201 jsonObjectSetKey( newObj, itr->key,
202 _jsonObjectEncodeClass(tmp, 0));
204 jsonIteratorFree(itr);
206 } else if( obj->type == JSON_ARRAY ) {
208 newObj = jsonNewObjectType(JSON_ARRAY);
210 for( i = 0; i != obj->size; i++ ) {
211 jsonObjectSetIndex( newObj, i,
212 _jsonObjectEncodeClass(jsonObjectGetIndex( obj, i ), 0 ));
216 newObj = jsonObjectClone(obj);
222 jsonObject* jsonObjectFindPath( const jsonObject* obj, const char* format, ...) {
223 if(!obj || !format || strlen(format) < 1) return NULL;
225 VA_LIST_TO_STRING(format);
228 char* tt; /* strtok storage */
230 /* special case where path starts with // (start anywhere) */
231 if(buf[0] == '/' && buf[1] == '/' && buf[2] != '\0') {
233 /* copy the path before strtok_r destroys it */
234 char* pathcopy = strdup(buf);
236 /* grab the root of the path */
237 token = strtok_r(buf, "/", &tt);
243 jsonObject* it = findMultiPath(obj, token, pathcopy + 1);
249 /* grab the root of the path */
250 token = strtok_r(buf, "/", &tt);
251 if(!token) return NULL;
254 obj = jsonObjectGetKeyConst(obj, token);
255 } while( (token = strtok_r(NULL, "/", &tt)) && obj);
257 return jsonObjectClone(obj);
261 /* --------------------------------------------------------------- */
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) {
268 if(!obj || ! root || !path) return NULL;
270 /* collect all of the potential objects */
271 jsonObject* arr = findMultiPathRecurse(obj, root);
273 /* path is just /root or /root/ */
274 if( strlen(root) + 2 >= strlen(path) ) {
279 /* container for fully matching objects */
280 jsonObject* newarr = jsonNewObjectType(JSON_ARRAY);
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);
288 if(thing) { //jsonObjectPush(newarr, thing);
289 if(thing->type == JSON_ARRAY) {
291 for( i = 0; i != thing->size; i++ )
292 jsonObjectPush(newarr, jsonObjectClone(jsonObjectGetIndex(thing,i)));
293 jsonObjectFree(thing);
295 jsonObjectPush(newarr, thing);
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) {
309 jsonObject* arr = jsonNewObjectType(JSON_ARRAY);
314 /* if the current object has a node that matches, add it */
316 const jsonObject* o = jsonObjectGetKeyConst(obj, root);
317 if(o) jsonObjectPush( arr, jsonObjectClone(o) );
319 jsonObject* tmp = NULL;
320 jsonObject* childarr;
321 jsonIterator* itr = jsonNewIterator(obj);
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)) );
331 jsonObjectFree(childarr);
334 jsonIteratorFree(itr);