From 47af5b4dc262987d61603dfe97abd774efe8e879 Mon Sep 17 00:00:00 2001 From: erickson Date: Fri, 1 Jun 2007 18:27:29 +0000 Subject: [PATCH] added uncommitted new JSON files to branch git-svn-id: svn://svn.open-ils.org/OpenSRF/branches/new-json@931 9efc2488-bf62-4759-914b-345cdb29e865 --- src/libstack/Makefile.JSON | 46 +++ src/libstack/legacy_json.c | 851 ++++++++++++++++++++++++++++++++++++++++ src/libstack/legacy_json.h | 86 ++++ src/libstack/osrf_json.h | 364 +++++++++++++++++ src/libstack/osrf_json_object.c | 333 ++++++++++++++++ src/libstack/osrf_json_parser.c | 632 +++++++++++++++++++++++++++++ src/libstack/osrf_json_tools.c | 437 +++++++++++++++++++++ src/libstack/osrf_json_utils.h | 131 +++++++ 8 files changed, 2880 insertions(+) create mode 100644 src/libstack/Makefile.JSON create mode 100644 src/libstack/legacy_json.c create mode 100644 src/libstack/legacy_json.h create mode 100644 src/libstack/osrf_json.h create mode 100644 src/libstack/osrf_json_object.c create mode 100644 src/libstack/osrf_json_parser.c create mode 100644 src/libstack/osrf_json_tools.c create mode 100644 src/libstack/osrf_json_utils.h diff --git a/src/libstack/Makefile.JSON b/src/libstack/Makefile.JSON new file mode 100644 index 0000000..da15e1f --- /dev/null +++ b/src/libstack/Makefile.JSON @@ -0,0 +1,46 @@ +# -------------------------------------------------------------- +# This makefile creates a libosrfjson.so library for a standlone +# JSON parser. +# +# usage: make -f Makefile.JSON +# +# -------------------------------------------------------------- + +#PG = -pg # enable profiling support +#G = -g # enable debugging +CFLAGS += $(G) -Wall $(PG) -O2 -fPIC -DJSON_IGNORE_COMMENTS -I ../utils/ +LDLIBS += +LDFLAGS += -L . $(PG) + +TARGETS = osrf_hash.o \ + osrf_list.o \ + osrf_json_parser.o \ + osrf_json_object.o \ + osrf_json_tools.o \ + ../utils/utils.o \ + ../utils/md5.o \ + ../utils/string_array.o \ + ../utils/log.o \ + + +all: libosrfjson.so + +osrf_hash.o: osrf_hash.c osrf_hash.h +osrf_list.o: osrf_list.c osrf_list.h +osrf_json_parser.o: osrf_json_parser.c osrf_json.h osrf_json_utils.h +osrf_json_object.o: osrf_json_object.c osrf_json.h osrf_json_utils.h +osrf_json_tools.o: osrf_json_tools.c osrf_json.h osrf_json_utils.h +../utils/utils.o: ../utils/utils.c ../utils/utils.h +../utils/md5.o: ../utils/md5.c ../utils/md5.h +../utils/string_array.o: ../utils/string_array.c ../utils/string_array.h +../utils/log.o: ../utils/log.c ../utils/log.h + + + +libosrfjson.so: $(TARGETS) + $(CC) -shared -W1 $(LDFLAGS) $(ARGETS) -o $@ + +clean: + rm -f $(TARGETS) libosrfjson.so + + diff --git a/src/libstack/legacy_json.c b/src/libstack/legacy_json.c new file mode 100644 index 0000000..516c282 --- /dev/null +++ b/src/libstack/legacy_json.c @@ -0,0 +1,851 @@ +/* +Copyright (C) 2006 Georgia Public Library Service +Bill Erickson + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + + +#include "legacy_json.h" + +/* keep a copy of the length of the current json string so we don't + * have to calculate it in each function + */ +int current_strlen; + + +jsonObject* legacy_jsonParseString( char* string) { + return json_parse_string( string ); +} + +jsonObject* legacy_jsonParseStringFmt( char* string, ... ) { + VA_LIST_TO_STRING(string); + return json_parse_string( VA_BUF ); +} + + +jsonObject* json_parse_string(char* string) { + + if(string == NULL) return NULL; + + current_strlen = strlen(string); + + if(current_strlen == 0) + return NULL; + + unsigned long index = 0; + + json_eat_ws(string, &index, 1, current_strlen); /* remove leading whitespace */ + if(index == current_strlen) return NULL; + + jsonObject* obj = jsonNewObject(NULL); + + int status = _json_parse_string(string, &index, obj, current_strlen); + if(!status) return obj; + + if(status == -2) { + jsonObjectFree(obj); + return NULL; + } + + return NULL; +} + + +int _json_parse_string(char* string, unsigned long* index, jsonObject* obj, int current_strlen) { + if( !string || !index || *index >= current_strlen) return -2; + + int status = 0; /* return code from parsing routines */ + char* classname = NULL; /* object class hint */ + json_eat_ws(string, index, 1, current_strlen); /* remove leading whitespace */ + + char c = string[*index]; + + /* remove any leading comments */ + if( c == '/' ) { + + while(1) { + (*index)++; /* move to second comment char */ + status = json_eat_comment(string, index, &classname, 1, current_strlen); + if(status) return status; + + json_eat_ws(string, index, 1, current_strlen); + c = string[*index]; + if(c != '/') + break; + } + } + + json_eat_ws(string, index, 1, current_strlen); /* remove leading whitespace */ + + if(*index >= current_strlen) + return -2; + + switch(c) { + + /* json string */ + case '"': + (*index)++; + status = json_parse_json_string(string, index, obj, current_strlen); break; + + /* json array */ + case '[': + (*index)++; + status = json_parse_json_array(string, index, obj, current_strlen); + break; + + /* json object */ + case '{': + (*index)++; + status = json_parse_json_object(string, index, obj, current_strlen); + break; + + /* NULL */ + case 'n': + case 'N': + status = json_parse_json_null(string, index, obj, current_strlen); + break; + + + /* true, false */ + case 'f': + case 'F': + case 't': + case 'T': + status = json_parse_json_bool(string, index, obj, current_strlen); + break; + + default: + if(is_number(c) || c == '.' || c == '-') { /* are we a number? */ + status = json_parse_json_number(string, index, obj, current_strlen); + if(status) return status; + break; + } + + (*index)--; + /* we should never get here */ + return json_handle_error(string, index, "_json_parse_string() final switch clause"); + } + + if(status) return status; + + json_eat_ws(string, index, 1, current_strlen); + + if( *index < current_strlen ) { + /* remove any trailing comments */ + c = string[*index]; + if( c == '/' ) { + (*index)++; + status = json_eat_comment(string, index, NULL, 0, current_strlen); + if(status) return status; + } + } + + if(classname){ + jsonObjectSetClass(obj, classname); + free(classname); + } + + return 0; +} + + +int json_parse_json_null(char* string, unsigned long* index, jsonObject* obj, int current_strlen) { + + if(*index >= (current_strlen - 3)) { + return json_handle_error(string, index, + "_parse_json_null(): invalid null" ); + } + + if(!strncasecmp(string + (*index), "null", 4)) { + (*index) += 4; + obj->type = JSON_NULL; + return 0; + } else { + return json_handle_error(string, index, + "_parse_json_null(): invalid null" ); + } +} + +/* should be at the first character of the bool at this point */ +int json_parse_json_bool(char* string, unsigned long* index, jsonObject* obj, int current_strlen) { + if( ! string || ! obj || *index >= current_strlen ) return -1; + + char* ret = "json_parse_json_bool(): truncated bool"; + + if( *index >= (current_strlen - 5)) + return json_handle_error(string, index, ret); + + if(!strncasecmp( string + (*index), "false", 5)) { + (*index) += 5; + obj->value.b = 0; + obj->type = JSON_BOOL; + return 0; + } + + if( *index >= (current_strlen - 4)) + return json_handle_error(string, index, ret); + + if(!strncasecmp( string + (*index), "true", 4)) { + (*index) += 4; + obj->value.b = 1; + obj->type = JSON_BOOL; + return 0; + } + + return json_handle_error(string, index, ret); +} + + +/* expecting the first character of the number */ +int json_parse_json_number(char* string, unsigned long* index, jsonObject* obj, int current_strlen) { + if( ! string || ! obj || *index >= current_strlen ) return -1; + + growing_buffer* buf = buffer_init(64); + char c = string[*index]; + + int done = 0; + int dot_seen = 0; + + /* negative number? */ + if(c == '-') { buffer_add(buf, "-"); (*index)++; } + + c = string[*index]; + + while(*index < current_strlen) { + + if(is_number(c)) { + buffer_add_char(buf, c); + } + + else if( c == '.' ) { + if(dot_seen) { + buffer_free(buf); + return json_handle_error(string, index, + "json_parse_json_number(): malformed json number"); + } + dot_seen = 1; + buffer_add_char(buf, c); + } else { + done = 1; break; + } + + (*index)++; + c = string[*index]; + if(done) break; + } + + obj->type = JSON_NUMBER; + obj->value.n = strtod(buf->buf, NULL); + buffer_free(buf); + return 0; +} + +/* index should point to the character directly following the '['. when done + * index will point to the character directly following the ']' character + */ +int json_parse_json_array(char* string, unsigned long* index, jsonObject* obj, int current_strlen) { + + if( ! string || ! obj || ! index || *index >= current_strlen ) return -1; + + int status = 0; + int in_parse = 0; /* true if this array already contains one item */ + obj->type = JSON_ARRAY; + int set = 0; + int done = 0; + + while(*index < current_strlen) { + + json_eat_ws(string, index, 1, current_strlen); + + if(string[*index] == ']') { + (*index)++; + done = 1; + break; + } + + if(in_parse) { + json_eat_ws(string, index, 1, current_strlen); + if(string[*index] != ',') { + return json_handle_error(string, index, + "json_parse_json_array(): array item not followed by a ','"); + } + (*index)++; + json_eat_ws(string, index, 1, current_strlen); + } + + jsonObject* item = jsonNewObject(NULL); + + #ifndef STRICT_JSON_READ + if(*index < current_strlen) { + if(string[*index] == ',' || string[*index] == ']') { + status = 0; + set = 1; + } + } + if(!set) status = _json_parse_string(string, index, item, current_strlen); + + #else + status = _json_parse_string(string, index, item, current_strlen); + #endif + + if(status) { jsonObjectFree(item); return status; } + jsonObjectPush(obj, item); + in_parse = 1; + set = 0; + } + + if(!done) + return json_handle_error(string, index, + "json_parse_json_array(): array not closed"); + + return 0; +} + + +/* index should point to the character directly following the '{'. when done + * index will point to the character directly following the '}' + */ +int json_parse_json_object(char* string, unsigned long* index, jsonObject* obj, int current_strlen) { + if( ! string || !obj || ! index || *index >= current_strlen ) return -1; + + obj->type = JSON_HASH; + int status; + int in_parse = 0; /* true if we've already added one item to this object */ + int set = 0; + int done = 0; + + while(*index < current_strlen) { + + json_eat_ws(string, index, 1, current_strlen); + + if(string[*index] == '}') { + (*index)++; + done = 1; + break; + } + + if(in_parse) { + if(string[*index] != ',') { + return json_handle_error(string, index, + "json_parse_json_object(): object missing ',' between elements" ); + } + (*index)++; + json_eat_ws(string, index, 1, current_strlen); + } + + /* first we grab the hash key */ + jsonObject* key_obj = jsonNewObject(NULL); + status = _json_parse_string(string, index, key_obj, current_strlen); + if(status) return status; + + if(key_obj->type != JSON_STRING) { + return json_handle_error(string, index, + "_json_parse_json_object(): hash key not a string"); + } + + char* key = key_obj->value.s; + + json_eat_ws(string, index, 1, current_strlen); + + if(string[*index] != ':') { + return json_handle_error(string, index, + "json_parse_json_object(): hash key not followed by ':' character"); + } + + (*index)++; + + /* now grab the value object */ + json_eat_ws(string, index, 1, current_strlen); + jsonObject* value_obj = jsonNewObject(NULL); + +#ifndef STRICT_JSON_READ + if(*index < current_strlen) { + if(string[*index] == ',' || string[*index] == '}') { + status = 0; + set = 1; + } + } + if(!set) + status = _json_parse_string(string, index, value_obj, current_strlen); + +#else + status = _json_parse_string(string, index, value_obj, current_strlen); +#endif + + if(status) return status; + + /* put the data into the object and continue */ + jsonObjectSetKey(obj, key, value_obj); + jsonObjectFree(key_obj); + in_parse = 1; + set = 0; + } + + if(!done) + return json_handle_error(string, index, + "json_parse_json_object(): object not closed"); + + return 0; +} + + + +/* when done, index will point to the character after the closing quote */ +int json_parse_json_string(char* string, unsigned long* index, jsonObject* obj, int current_strlen) { + if( ! string || ! index || *index >= current_strlen ) return -1; + + int in_escape = 0; + int done = 0; + growing_buffer* buf = buffer_init(64); + + while(*index < current_strlen) { + + char c = string[*index]; + + switch(c) { + + case '\\': + if(in_escape) { + buffer_add(buf, "\\"); + in_escape = 0; + } else + in_escape = 1; + break; + + case '"': + if(in_escape) { + buffer_add(buf, "\""); + in_escape = 0; + } else + done = 1; + break; + + case 't': + if(in_escape) { + buffer_add(buf,"\t"); + in_escape = 0; + } else + buffer_add_char(buf, c); + break; + + case 'b': + if(in_escape) { + buffer_add(buf,"\b"); + in_escape = 0; + } else + buffer_add_char(buf, c); + break; + + case 'f': + if(in_escape) { + buffer_add(buf,"\f"); + in_escape = 0; + } else + buffer_add_char(buf, c); + break; + + case 'r': + if(in_escape) { + buffer_add(buf,"\r"); + in_escape = 0; + } else + buffer_add_char(buf, c); + break; + + case 'n': + if(in_escape) { + buffer_add(buf,"\n"); + in_escape = 0; + } else + buffer_add_char(buf, c); + break; + + case 'u': + if(in_escape) { + (*index)++; + + if(*index >= (current_strlen - 4)) { + buffer_free(buf); + return json_handle_error(string, index, + "json_parse_json_string(): truncated escaped unicode"); } + + char buff[5]; + memset(buff,0,5); + memcpy(buff, string + (*index), 4); + + + /* ----------------------------------------------------------------------- */ + /* ----------------------------------------------------------------------- */ + /* The following chunk was borrowed with permission from + json-c http://oss.metaparadigm.com/json-c/ */ + unsigned char utf_out[3]; + memset(utf_out,0,3); + + #define hexdigit(x) ( ((x) <= '9') ? (x) - '0' : ((x) & 7) + 9) + + unsigned int ucs_char = + (hexdigit(string[*index] ) << 12) + + (hexdigit(string[*index + 1]) << 8) + + (hexdigit(string[*index + 2]) << 4) + + hexdigit(string[*index + 3]); + + if (ucs_char < 0x80) { + utf_out[0] = ucs_char; + buffer_add(buf, (char*) utf_out); + + } else if (ucs_char < 0x800) { + utf_out[0] = 0xc0 | (ucs_char >> 6); + utf_out[1] = 0x80 | (ucs_char & 0x3f); + buffer_add(buf, (char*) utf_out); + + } else { + utf_out[0] = 0xe0 | (ucs_char >> 12); + utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f); + utf_out[2] = 0x80 | (ucs_char & 0x3f); + buffer_add(buf, (char*) utf_out); + } + /* ----------------------------------------------------------------------- */ + /* ----------------------------------------------------------------------- */ + + (*index) += 3; + in_escape = 0; + + } else { + + buffer_add_char(buf, c); + } + + break; + + default: + buffer_add_char(buf, c); + } + + (*index)++; + if(done) break; + } + + jsonObjectSetString(obj, buf->buf); + buffer_free(buf); + return 0; +} + + +void json_eat_ws(char* string, unsigned long* index, int eat_all, int current_strlen) { + if( ! string || ! index ) return; + if(*index >= current_strlen) + return; + + if( eat_all ) { /* removes newlines, etc */ + while(string[*index] == ' ' || + string[*index] == '\n' || + string[*index] == '\t') + (*index)++; + } + + else + while(string[*index] == ' ') (*index)++; +} + + +/* index should be at the '*' character at the beginning of the comment. + * when done, index will point to the first character after the final / + */ +int json_eat_comment(char* string, unsigned long* index, char** buffer, int parse_class, int current_strlen) { + if( ! string || ! index || *index >= current_strlen ) return -1; + + + if(string[*index] != '*' && string[*index] != '/' ) + return json_handle_error(string, index, + "json_eat_comment(): invalid character after /"); + + /* chop out any // style comments */ + if(string[*index] == '/') { + (*index)++; + char c = string[*index]; + while(*index < current_strlen) { + (*index)++; + if(c == '\n') + return 0; + c = string[*index]; + } + return 0; + } + + (*index)++; + + int on_star = 0; /* true if we just saw a '*' character */ + + /* we're just past the '*' */ + if(!parse_class) { /* we're not concerned with class hints */ + while(*index < current_strlen) { + if(string[*index] == '/') { + if(on_star) { + (*index)++; + return 0; + } + } + + if(string[*index] == '*') on_star = 1; + else on_star = 0; + + (*index)++; + } + return 0; + } + + + + growing_buffer* buf = buffer_init(64); + + int first_dash = 0; + int second_dash = 0; + int third_dash = 0; + int fourth_dash = 0; + + int in_hint = 0; + int done = 0; + + /*--S hint--*/ /* <-- Hints look like this */ + /*--E hint--*/ + + while(*index < current_strlen) { + char c = string[*index]; + + switch(c) { + + case '-': + on_star = 0; + if(third_dash) fourth_dash = 1; + else if(in_hint) third_dash = 1; + else if(first_dash) second_dash = 1; + else first_dash = 1; + break; + + case 'S': + on_star = 0; + if(second_dash && !in_hint) { + (*index)++; + json_eat_ws(string, index, 1, current_strlen); + (*index)--; /* this will get incremented at the bottom of the loop */ + in_hint = 1; + break; + } + + if(second_dash && in_hint) { + buffer_add_char(buf, c); + break; + } + + case 'E': + on_star = 0; + if(second_dash && !in_hint) { + (*index)++; + json_eat_ws(string, index, 1, current_strlen); + (*index)--; /* this will get incremented at the bottom of the loop */ + in_hint = 1; + break; + } + + if(second_dash && in_hint) { + buffer_add_char(buf, c); + break; + } + + case '*': + on_star = 1; + break; + + case '/': + if(on_star) + done = 1; + else + on_star = 0; + break; + + default: + on_star = 0; + if(in_hint) + buffer_add_char(buf, c); + } + + (*index)++; + if(done) break; + } + + if( buf->n_used > 0 && buffer) + *buffer = buffer_data(buf); + + buffer_free(buf); + return 0; +} + +int is_number(char c) { + switch(c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return 1; + } + return 0; +} + +int json_handle_error(char* string, unsigned long* index, char* err_msg) { + + char buf[60]; + memset(buf, 0, 60); + + if(*index > 30) + strncpy( buf, string + (*index - 30), 59 ); + else + strncpy( buf, string, 59 ); + + fprintf(stderr, + "\nError parsing json string at charracter %c " + "(code %d) and index %ld\nString length: %d\nMsg:\t%s\nNear:\t%s\nFull String:\t%s\n", + string[*index], string[*index], *index, current_strlen, err_msg, buf, string ); + + return -1; +} + + +jsonObject* legacy_jsonParseFile( const char* filename ) { + return json_parse_file( filename ); +} + +jsonObject* json_parse_file(const char* filename) { + if(!filename) return NULL; + char* data = file_to_string(filename); + jsonObject* o = json_parse_string(data); + free(data); + return o; +} + + + +char* legacy_jsonObjectToJSON( const jsonObject* obj ) { + + if(obj == NULL) return strdup("null"); + + growing_buffer* buf = buffer_init(64); + + /* add class hints if we have a class name */ + if(obj->classname) { + buffer_add(buf,"/*--S "); + buffer_add(buf,obj->classname); + buffer_add(buf, "--*/"); + } + + switch( obj->type ) { + + case JSON_BOOL: + if(obj->value.b) buffer_add(buf, "true"); + else buffer_add(buf, "false"); + break; + + case JSON_NUMBER: { + double x = obj->value.n; + + /* if the number does not need to be a double, + turn it into an int on the way out */ + if( x == (int) x ) { + INT_TO_STRING((int)x); + buffer_add(buf, INTSTR); + + } else { + DOUBLE_TO_STRING(x); + buffer_add(buf, DOUBLESTR); + } + break; + } + + case JSON_NULL: + buffer_add(buf, "null"); + break; + + case JSON_STRING: + buffer_add(buf, "\""); + char* data = obj->value.s; + int len = strlen(data); + + char* output = uescape(data, len, 1); + buffer_add(buf, output); + free(output); + buffer_add(buf, "\""); + break; + + case JSON_ARRAY: + buffer_add(buf, "["); + int i; + for( i = 0; i!= obj->size; i++ ) { + const jsonObject* x = jsonObjectGetIndex(obj,i); + char* data = legacy_jsonObjectToJSON(x); + buffer_add(buf, data); + free(data); + if(i != obj->size - 1) + buffer_add(buf, ","); + } + buffer_add(buf, "]"); + break; + + case JSON_HASH: + + buffer_add(buf, "{"); + jsonObjectIterator* itr = jsonNewObjectIterator(obj); + jsonObject* tmp; + + while( (tmp = jsonObjectIteratorNext(itr)) ) { + + buffer_add(buf, "\""); + + char* key = itr->key; + int len = strlen(key); + char* output = uescape(key, len, 1); + buffer_add(buf, output); + free(output); + + buffer_add(buf, "\":"); + char* data = legacy_jsonObjectToJSON(tmp); + buffer_add(buf, data); + if(jsonObjectIteratorHasNext(itr)) + buffer_add(buf, ","); + free(data); + } + + jsonObjectIteratorFree(itr); + buffer_add(buf, "}"); + break; + + default: + fprintf(stderr, "Unknown object type %d\n", obj->type); + break; + + } + + /* close out the object hint */ + if(obj->classname) { + buffer_add(buf, "/*--E "); + buffer_add(buf, obj->classname); + buffer_add(buf, "--*/"); + } + + char* data = buffer_data(buf); + buffer_free(buf); + return data; +} diff --git a/src/libstack/legacy_json.h b/src/libstack/legacy_json.h new file mode 100644 index 0000000..27eac3f --- /dev/null +++ b/src/libstack/legacy_json.h @@ -0,0 +1,86 @@ +/* +Copyright (C) 2005 Georgia Public Library Service +Bill Erickson + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + + + + +/* --------------------------------------------------------------------------------------- + JSON parser. + * --------------------------------------------------------------------------------------- */ +#ifndef LEGACY_JSON_H +#define LEGACY_JSON_H + +#include "osrf_json.h" + + + +/* Parses the given JSON string and returns the built object. + * returns NULL (and prints parser error to stderr) on error. + */ + +jsonObject* json_parse_string(char* string); + +jsonObject* legacy_jsonParseString(char* string); +jsonObject* legacy_jsonParseStringFmt( char* string, ... ); + +jsonObject* json_parse_file( const char* filename ); + +jsonObject* legacy_jsonParseFile( const char* string ); + + + +/* does the actual parsing work. returns 0 on success. -1 on error and + * -2 if there was no object to build (string was all comments) + */ +int _json_parse_string(char* string, unsigned long* index, jsonObject* obj, int current_strlen); + +/* returns 0 on success and turns obj into a string object */ +int json_parse_json_string(char* string, unsigned long* index, jsonObject* obj, int current_strlen); + +/* returns 0 on success and turns obj into a number or double object */ +int json_parse_json_number(char* string, unsigned long* index, jsonObject* obj, int current_strlen); + +/* returns 0 on success and turns obj into an 'object' object */ +int json_parse_json_object(char* string, unsigned long* index, jsonObject* obj, int current_strlen); + +/* returns 0 on success and turns object into an array object */ +int json_parse_json_array(char* string, unsigned long* index, jsonObject* obj, int current_strlen); + +/* churns through whitespace and increments index as it goes. + * eat_all == true means we should eat newlines, tabs + */ +void json_eat_ws(char* string, unsigned long* index, int eat_all, int current_strlen); + +int json_parse_json_bool(char* string, unsigned long* index, jsonObject* obj, int current_strlen); + +/* removes comments from a json string. if the comment contains a class hint + * and class_hint isn't NULL, an allocated char* with the class name will be + * shoved into *class_hint. returns 0 on success, -1 on parse error. + * 'index' is assumed to be at the second character (*) of the comment + */ +int json_eat_comment(char* string, unsigned long* index, char** class_hint, int parse_class, int current_strlen); + +/* prints a useful error message to stderr. always returns -1 */ +int json_handle_error(char* string, unsigned long* index, char* err_msg); + +/* returns true if c is 0-9 */ +int is_number(char c); + +int json_parse_json_null(char* string, unsigned long* index, jsonObject* obj, int current_strlen); + + +char* legacy_jsonObjectToJSON( const jsonObject* obj ); + +#endif diff --git a/src/libstack/osrf_json.h b/src/libstack/osrf_json.h new file mode 100644 index 0000000..2c64b93 --- /dev/null +++ b/src/libstack/osrf_json.h @@ -0,0 +1,364 @@ +/* +Copyright (C) 2006 Georgia Public Library Service +Bill Erickson + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + + +#include "utils.h" +#include "osrf_list.h" +#include "osrf_hash.h" + +#ifndef _JSON_H +#define _JSON_H + + +/* parser states */ +#define JSON_STATE_IN_OBJECT 0x1 +#define JSON_STATE_IN_ARRAY 0x2 +#define JSON_STATE_IN_STRING 0x4 +#define JSON_STATE_IN_UTF 0x8 +#define JSON_STATE_IN_ESCAPE 0x10 +#define JSON_STATE_IN_KEY 0x20 +#define JSON_STATE_IN_NULL 0x40 +#define JSON_STATE_IN_TRUE 0x80 +#define JSON_STATE_IN_FALSE 0x100 +#define JSON_STATE_IN_NUMBER 0x200 +#define JSON_STATE_IS_INVALID 0x400 +#define JSON_STATE_IS_DONE 0x800 +#define JSON_STATE_START_COMMEN 0x1000 +#define JSON_STATE_IN_COMMENT 0x2000 +#define JSON_STATE_END_COMMENT 0x4000 + + +/* object and array (container) states are pushed onto a stack so we + * can keep track of the object nest. All other states are + * simply stored in the state field of the parser */ +#define JSON_STATE_SET(ctx,s) ctx->state |= s; /* set a state */ +#define JSON_STATE_REMOVE(ctx,s) ctx->state &= ~s; /* unset a state */ +#define JSON_STATE_CHECK(ctx,s) (ctx->state & s) ? 1 : 0 /* check if a state is set */ +#define JSON_STATE_POP(ctx) osrfListPop( ctx->stateStack ); /* remove a state from the stack */ +#define JSON_STATE_PUSH(ctx, state) osrfListPush( ctx->stateStack,(void*) state );/* push a state on the stack */ +#define JSON_STATE_PEEK(ctx) osrfListGetIndex(ctx->stateStack, ctx->stateStack->size -1) /* check which container type we're currently in */ +#define JSON_STATE_CHECK_STACK(ctx, s) (JSON_STATE_PEEK(ctx) == (void*) s ) ? 1 : 0 /* compare stack values */ + +/* JSON types */ +#define JSON_HASH 0 +#define JSON_ARRAY 1 +#define JSON_STRING 2 +#define JSON_NUMBER 3 +#define JSON_NULL 4 +#define JSON_BOOL 5 + +#define JSON_PARSE_LAST_CHUNK 0x1 /* this is the last part of the string we're parsing */ + +#define JSON_PARSE_FLAG_CHECK(ctx, f) (ctx->flags & f) ? 1 : 0 /* check if a parser state is set */ + +#ifndef JSON_CLASS_KEY +#define JSON_CLASS_KEY "__c" +#endif +#ifndef JSON_DATA_KEY +#define JSON_DATA_KEY "__p" +#endif + + +struct jsonParserContextStruct { + int state; /* what are we currently parsing */ + char* chunk; /* the chunk we're currently parsing */ + int index; /* where we are in parsing the current chunk */ + int chunksize; /* the size of the current chunk */ + int flags; /* parser flags */ + osrfList* stateStack; /* represents the nest of object/array states */ + growing_buffer* buffer; /* used to hold JSON strings, number, true, false, and null sequences */ + growing_buffer* utfbuf; /* holds the current unicode characters */ + void* userData; /* opaque user pointer. we ignore this */ + struct jsonParserHandlerStruct* handler; /* the event handler struct */ +}; +typedef struct jsonParserContextStruct jsonParserContext; + +struct jsonParserHandlerStruct { + void (*handleStartObject) (void* userData); + void (*handleObjectKey) (void* userData, char* key); + void (*handleEndObject) (void* userData); + void (*handleStartArray) (void* userData); + void (*handleEndArray) (void* userData); + void (*handleNull) (void* userData); + void (*handleString) (void* userData, char* string); + void (*handleBool) (void* userData, int boolval); + void (*handleNumber) (void* userData, long double num); + void (*handleError) (void* userData, char* err, ...); +}; +typedef struct jsonParserHandlerStruct jsonParserHandler; + +struct _jsonObjectStruct { + unsigned long size; /* number of sub-items */ + char* classname; /* optional class hint (not part of the JSON spec) */ + int type; /* JSON type */ + struct _jsonObjectStruct* parent; /* who we're attached to */ + union __jsonValue { /* cargo */ + osrfHash* h; /* object container */ + osrfList* l; /* array container */ + char* s; /* string */ + int b; /* bool */ + long double n; /* number */ + } value; +}; +typedef struct _jsonObjectStruct jsonObject; + +struct _jsonObjectIteratorStruct { + jsonObject* obj; /* the object we're traversing */ + osrfHashIterator* hashItr; /* the iterator for this hash */ + char* key; /* if this object is an object, the current key */ + unsigned long index; /* if this object is an array, the index */ +}; +typedef struct _jsonObjectIteratorStruct jsonObjectIterator; + + + +/** + * Allocates a new parser context object + * @param handler The event handler struct + * @param userData Opaque user pointer which is available in callbacks + * and ignored by the parser + * @return An allocated parser context, NULL on error + */ +jsonParserContext* jsonNewParser( jsonParserHandler* handler, void* userData); + +/** + * Deallocates a parser context + * @param ctx The context object + */ +void jsonParserFree( jsonParserContext* ctx ); + +/** + * Parse a chunk of data. + * @param ctx The parser context + * @param data The data to parse + * @param datalen The size of the chunk to parser + * @param flags Reserved + */ +int jsonParseChunk( jsonParserContext* ctx, char* data, int datalen, int flags ); + + +/** + * Parses a JSON string; + * @param str The string to parser + * @return The resulting JSON object or NULL on error + */ +jsonObject* jsonParseString( char* str ); +jsonObject* jsonParseStringRaw( char* str ); + +jsonObject* jsonParseStringFmt( char* str, ... ); + +/** + * Parses a JSON string; + * @param str The string to parser + * @return The resulting JSON object or NULL on error + */ +jsonObject* jsonParseStringHandleError( void (*errorHandler) (const char*), char* str, ... ); + + + +/** + * Creates a new json object + * @param data The string data this object will hold if + * this object happens to be a JSON_STRING, NULL otherwise + * @return The allocated json object. Must be freed with + * jsonObjectFree() + */ +jsonObject* jsonNewObject(char* data, ...); + +/** + * Creates a new object of the given type + */ +jsonObject* jsonNewObjectType(int type); + +/** + * Creates a new number object + */ +jsonObject* jsonNewNumberObject( long double num ); + +/** + * Deallocates an object + */ +void jsonObjectFree( jsonObject* o ); + +/** + * Forces the given object to become an array (if it isn't already one) + * and pushes the new object into the array + */ +unsigned long jsonObjectPush(jsonObject* o, jsonObject* newo); + +/** + * Forces the given object to become a hash (if it isn't already one) + * and assigns the new object to the key of the hash + */ +unsigned long jsonObjectSetKey( + jsonObject* o, const char* key, jsonObject* newo); + + +/** + * Turns the object into a JSON string. The string must be freed by the caller */ +char* jsonObjectToJSON( const jsonObject* obj ); +char* jsonObjectToJSONRaw( const jsonObject* obj ); + + +/** + * Retrieves the object at the given key + */ +jsonObject* jsonObjectGetKey( const jsonObject* obj, const char* key ); + + + + + + +/** Allocates a new iterator + @param obj The object over which to iterate. +*/ +jsonObjectIterator* jsonNewObjectIterator(const jsonObject* obj); + + +/** + De-allocates an iterator + @param iter The iterator object to free +*/ +void jsonObjectIteratorFree(jsonObjectIterator* iter); + +/** + Returns the object_node currently pointed to by the iterator + and increments the pointer to the next node + @param iter The iterator in question. + */ +jsonObject* jsonObjectIteratorNext(jsonObjectIterator* iter); + + +/** + @param iter The iterator. + @return True if there is another node after the current node. + */ +int jsonObjectIteratorHasNext(const jsonObjectIterator* iter); + + +/** + Returns a pointer to the object at the given index. This call is + only valid if the object has a type of JSON_ARRAY. + @param obj The object + @param index The position within the object + @return The object at the given index. +*/ +jsonObject* jsonObjectGetIndex( const jsonObject* obj, unsigned long index ); + + +/* removes (and deallocates) the object at the given index (if one exists) and inserts + * the new one. returns the size on success, -1 on error + * If obj is NULL, inserts a new object into the list with is_null set to true + */ +unsigned long jsonObjectSetIndex(jsonObject* dest, unsigned long index, jsonObject* newObj); + +/* removes the object at the given index and, if more items exist, + * re-indexes (shifts down by 1) the rest of the objects in the array + */ +unsigned long jsonObjectRemoveIndex(jsonObject* dest, unsigned long index); + +/* removes (and deallocates) the object with key 'key' if it exists */ +unsigned long jsonObjectRemoveKey( jsonObject* dest, const char* key); + +/* returns a pointer to the string data held by this object if this object + is a string. Otherwise returns NULL*/ +char* jsonObjectGetString(const jsonObject*); + +long double jsonObjectGetNumber( const jsonObject* obj ); + +/* sets the string data */ +void jsonObjectSetString(jsonObject* dest, const char* string); + +/* sets the number value for the object */ +void jsonObjectSetNumber(jsonObject* dest, double num); + +/* sets the class hint for this object */ +void jsonObjectSetClass(jsonObject* dest, const char* classname ); + +int jsonBoolIsTrue( jsonObject* boolObj ); + + +jsonObject* jsonObjectClone( const jsonObject* o ); + + +/* tries to extract the string data from an object. + if object -> NULL (the C NULL) + if array -> NULL + if null -> NULL + if bool -> NULL + if string/number the string version of either of those + The caller is responsible for freeing the returned string + */ +char* jsonObjectToSimpleString( const jsonObject* o ); + + + +/* provides an XPATH style search interface (e.g. /some/node/here) and + return the object at that location if one exists. Naturally, + every element in the path must be a proper object ("hash" / {}). + Returns NULL if the specified node is not found + Note also that the object returned is a clone and + must be freed by the caller +*/ +jsonObject* jsonObjectFindPath( const jsonObject* obj, char* path, ... ); + + +/* formats a JSON string from printing. User must free returned string */ +char* jsonFormatString( const char* jsonString ); + +/* sets the error handler for all parsers */ +void jsonSetGlobalErrorHandler(void (*errorHandler) (const char*)); + +jsonObject* jsonParseFile( char* filename ); + +/* ------------------------------------------------------------------------- */ +/** + * The following methods provide a ficility for serializing and + * deserializing "classed" JSON objects. To give a JSON object a + * class, simply call jsonObjectSetClass(). + * Then, calling jsonObjectEncodeClass() will convert the JSON + * object (and any sub-objects) to a JSON object with class + * wrapper objects like so: + * { _c : "classname", _d : } + * In this example _c is the class key and _d is the data (object) + * key. The keys are defined by the constants + * OSRF_JSON_CLASS_KEY and OSRF_JSON_DATA_KEY + * To revive a serialized object, simply call + * jsonObjectDecodeClass() + */ + + +/** Converts a class-wrapped object into an object with the + * classname set + * Caller must free the returned object + */ +jsonObject* jsonObjectDecodeClass( jsonObject* obj ); + + +/** Converts an object with a classname into a + * class-wrapped (serialized) object + * Caller must free the returned object + */ +jsonObject* jsonObjectEncodeClass( jsonObject* obj ); + +/* ------------------------------------------------------------------------- */ + + +/** + * Generates an XML representation of a JSON object */ +char* jsonObjectToXML(jsonObject*); + +#endif diff --git a/src/libstack/osrf_json_object.c b/src/libstack/osrf_json_object.c new file mode 100644 index 0000000..71fc181 --- /dev/null +++ b/src/libstack/osrf_json_object.c @@ -0,0 +1,333 @@ +/* +Copyright (C) 2006 Georgia Public Library Service +Bill Erickson + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "osrf_json.h" +#include "osrf_json_utils.h" +jsonObject* jsonNewObject(char* data, ...) { + + jsonObject* o; + OSRF_MALLOC(o, sizeof(jsonObject)); + o->type = JSON_NULL; + + if(data) { + VA_LIST_TO_STRING(data); + o->type = JSON_STRING; + o->value.s = strdup(VA_BUF); + } + + return o; +} + +jsonObject* jsonNewNumberObject( long double num ) { + jsonObject* o = jsonNewObject(NULL); + o->type = JSON_NUMBER; + o->value.n = num; + return o; +} + +jsonObject* jsonNewObjectType(int type) { + jsonObject* o = jsonNewObject(NULL); + o->type = type; + return o; +} + +void jsonObjectFree( jsonObject* o ) { + + if(!o || o->parent) return; + free(o->classname); + + switch(o->type) { + case JSON_HASH : osrfHashFree(o->value.h); break; + case JSON_ARRAY : osrfListFree(o->value.l); break; + case JSON_STRING : free(o->value.s); break; + } + free(o); +} + +static void _jsonFreeHashItem(char* key, void* item){ + if(!item) return; + jsonObject* o = (jsonObject*) item; + o->parent = NULL; /* detach the item */ + jsonObjectFree(o); +} +static void _jsonFreeListItem(void* item){ + if(!item) return; + jsonObject* o = (jsonObject*) item; + o->parent = NULL; /* detach the item */ + jsonObjectFree(o); +} + + +unsigned long jsonObjectPush(jsonObject* o, jsonObject* newo) { + if(!(o && newo)) return -1; + JSON_INIT_CLEAR(o, JSON_ARRAY); + newo->parent = o; + osrfListPush( o->value.l, newo ); + o->size = o->value.l->size; + return o->size; +} + +unsigned long jsonObjectSetIndex(jsonObject* dest, unsigned long index, jsonObject* newObj) { + if(!(dest && newObj)) return -1; + JSON_INIT_CLEAR(dest, JSON_ARRAY); + newObj->parent = dest; + osrfListSet( dest->value.l, newObj, index ); + dest->size = dest->value.l->size; + return dest->value.l->size; +} + +unsigned long jsonObjectSetKey( + jsonObject* o, const char* key, jsonObject* newo) { + if(!(o && key && newo)) return -1; + JSON_INIT_CLEAR(o, JSON_HASH); + newo->parent = o; + osrfHashSet( o->value.h, newo, key ); + o->size = o->value.h->size; + return o->size; +} + +jsonObject* jsonObjectGetKey( const jsonObject* obj, const char* key ) { + if(!(obj && obj->type == JSON_HASH && obj->value.h && key)) return NULL; + return osrfHashGet( obj->value.h, key); +} + +char* jsonObjectToJSON( const jsonObject* obj ) { + jsonObject* obj2 = jsonObjectEncodeClass( (jsonObject*) obj); + char* json = jsonObjectToJSONRaw(obj2); + jsonObjectFree(obj2); + return json; +} + +char* jsonObjectToJSONRaw( const jsonObject* obj ) { + if(!obj) return NULL; + growing_buffer* buf = buffer_init(32); + + switch(obj->type) { + + case JSON_BOOL : + if(obj->value.b) OSRF_BUFFER_ADD(buf, "true"); + else OSRF_BUFFER_ADD(buf, "false"); + break; + + case JSON_NUMBER: { + long double x = obj->value.n; + if( x == (long) x ) { + LONG_TO_STRING((long)x); + OSRF_BUFFER_ADD(buf, LONGSTR); + + } else { + LONG_DOUBLE_TO_STRING(x); + OSRF_BUFFER_ADD(buf, LONGDOUBLESTR); + } + break; + } + + case JSON_NULL: + OSRF_BUFFER_ADD(buf, "null"); + break; + + case JSON_STRING: + OSRF_BUFFER_ADD(buf, "\""); + char* data = obj->value.s; + int len = strlen(data); + + char* output = uescape(data, len, 1); + OSRF_BUFFER_ADD(buf, output); + free(output); + OSRF_BUFFER_ADD(buf, "\""); + break; + + case JSON_ARRAY: { + OSRF_BUFFER_ADD(buf, "["); + if( obj->value.l ) { + int i; + for( i = 0; i != obj->value.l->size; i++ ) { + char* json = jsonObjectToJSON(osrfListGetIndex(obj->value.l, i)); + if(i > 0) OSRF_BUFFER_ADD(buf, ","); + OSRF_BUFFER_ADD(buf, json); + free(json); + } + } + OSRF_BUFFER_ADD(buf, "]"); + break; + } + + case JSON_HASH: { + + OSRF_BUFFER_ADD(buf, "{"); + osrfHashIterator* itr = osrfNewHashIterator(obj->value.h); + jsonObject* item; + int i = 0; + + while( (item = osrfHashIteratorNext(itr)) ) { + if(i++ > 0) OSRF_BUFFER_ADD(buf, ","); + buffer_fadd(buf, "\"%s\":", itr->current); + char* json = jsonObjectToJSON(item); + OSRF_BUFFER_ADD(buf, json); + free(json); + } + + osrfHashIteratorFree(itr); + OSRF_BUFFER_ADD(buf, "}"); + break; + } + } + + char* data = buffer_data(buf); + buffer_free(buf); + return data; +} + + +jsonObjectIterator* jsonNewObjectIterator(const jsonObject* obj) { + if(!obj) return NULL; + jsonObjectIterator* itr; + OSRF_MALLOC(itr, sizeof(jsonObjectIterator)); + + itr->obj = (jsonObject*) obj; + itr->index = 0; + itr->key = NULL; + + if( obj->type == JSON_HASH ) + itr->hashItr = osrfNewHashIterator(obj->value.h); + + return itr; +} + +void jsonObjectIteratorFree(jsonObjectIterator* itr) { + if(!itr) return; + free(itr->key); + osrfHashIteratorFree(itr->hashItr); + free(itr); +} + +jsonObject* jsonObjectIteratorNext(jsonObjectIterator* itr) { + if(!(itr && itr->obj)) return NULL; + if( itr->obj->type == JSON_HASH ) { + if(!itr->hashItr) return NULL; + jsonObject* item = osrfHashIteratorNext(itr->hashItr); + free(itr->key); + itr->key = strdup(itr->hashItr->current); + return item; + } else { + return jsonObjectGetIndex( itr->obj, itr->index++ ); + } +} + +int jsonObjectIteratorHasNext(const jsonObjectIterator* itr) { + if(!(itr && itr->obj)) return 0; + if( itr->obj->type == JSON_HASH ) + return osrfHashIteratorHasNext( itr->hashItr ); + return (itr->index < itr->obj->size) ? 1 : 0; +} + +jsonObject* jsonObjectGetIndex( const jsonObject* obj, unsigned long index ) { + if(!obj) return NULL; + return (obj->type == JSON_ARRAY) ? + osrfListGetIndex(obj->value.l, index) : NULL; +} + + + +unsigned long jsonObjectRemoveIndex(jsonObject* dest, unsigned long index) { + if( dest && dest->type == JSON_ARRAY ) { + osrfListRemove(dest->value.l, index); + return dest->value.l->size; + } + return -1; +} + + +unsigned long jsonObjectRemoveKey( jsonObject* dest, const char* key) { + if( dest && key && dest->type == JSON_HASH ) { + osrfHashRemove(dest->value.h, key); + return 1; + } + return -1; +} + +char* jsonObjectGetString(const jsonObject* obj) { + return (obj && obj->type == JSON_STRING) ? obj->value.s : NULL; +} + +long double jsonObjectGetNumber( const jsonObject* obj ) { + return (obj && obj->type == JSON_NUMBER) ? obj->value.n : 0; +} + +void jsonObjectSetString(jsonObject* dest, const char* string) { + if(!(dest && string)) return; + JSON_INIT_CLEAR(dest, JSON_STRING); + free(dest->value.s); + dest->value.s = strdup(string); +} + +void jsonObjectSetNumber(jsonObject* dest, double num) { + if(!dest) return; + JSON_INIT_CLEAR(dest, JSON_NUMBER); + dest->value.n = num; +} + +void jsonObjectSetClass(jsonObject* dest, const char* classname ) { + if(!(dest && classname)) return; + free(dest->classname); + dest->classname = strdup(classname); +} + +jsonObject* jsonObjectClone( const jsonObject* o ) { + if(!o) return NULL; + char* json = jsonObjectToJSONRaw(o); + jsonObject* oo = jsonParseStringRaw(json); + oo->type = o->type; + jsonObjectSetClass(oo, o->classname); + free(json); + return oo; +} + +int jsonBoolIsTrue( jsonObject* boolObj ) { + if( boolObj && boolObj->type == JSON_BOOL && boolObj->value.b ) + return 1; + return 0; +} + + +char* jsonObjectToSimpleString( const jsonObject* o ) { + if(!o) return NULL; + + char* value = NULL; + + switch( o->type ) { + + case JSON_NUMBER: { + + if( o->value.n == (int) o->value.n ) { + LONG_TO_STRING((long) o->value.n); + value = strdup(LONGSTR); + + } else { + LONG_DOUBLE_TO_STRING(o->value.n); + value = strdup(LONGDOUBLESTR); + } + + break; + } + + case JSON_STRING: + value = strdup(o->value.s); + } + + return value; +} + + diff --git a/src/libstack/osrf_json_parser.c b/src/libstack/osrf_json_parser.c new file mode 100644 index 0000000..37ab058 --- /dev/null +++ b/src/libstack/osrf_json_parser.c @@ -0,0 +1,632 @@ +/* +Copyright (C) 2006 Georgia Public Library Service +Bill Erickson + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "osrf_json.h" +#include "osrf_json_utils.h" +#include + + +/* if the client sets a global error handler, this will point to it */ +static void (*jsonClientErrorCallback) (const char*) = NULL; + +/* these are the handlers for our internal parser */ +static jsonParserHandler jsonInternalParserHandlerStruct = { + _jsonHandleStartObject, + _jsonHandleObjectKey, + _jsonHandleEndObject, + _jsonHandleStartArray, + _jsonHandleEndArray, + _jsonHandleNull, + _jsonHandleString, + _jsonHandleBool, + _jsonHandleNumber, + _jsonHandleError +}; +static jsonParserHandler* + jsonInternalParserHandler = &jsonInternalParserHandlerStruct; + + +jsonParserContext* jsonNewParser( jsonParserHandler* handler, void* userData) { + jsonParserContext* ctx; + OSRF_MALLOC(ctx, sizeof(jsonParserContext)); + ctx->stateStack = osrfNewList(); + ctx->buffer = buffer_init(512); + ctx->utfbuf = buffer_init(5); + ctx->handler = handler; + ctx->state = 0; + ctx->index = 0; + ctx->chunk = NULL; + ctx->userData = userData; + return ctx; +} + +void jsonParserFree( jsonParserContext* ctx ) { + if(!ctx) return; + buffer_free(ctx->buffer); + buffer_free(ctx->utfbuf); + osrfListFree(ctx->stateStack); + free(ctx); +} + + +void jsonSetGlobalErrorHandler(void (*errorHandler) (const char*)) { + jsonClientErrorCallback = errorHandler; +} + + +int _jsonParserError( jsonParserContext* ctx, char* err, ... ) { + if( ctx->handler->handleError ) { + VA_LIST_TO_STRING(err); + int pre = ctx->index - 15; + int post = ctx->index + 15; + while( pre < 0 ) pre++; + while( post >= ctx->chunksize ) post--; + int l = post - pre; + char buf[l]; + memset(buf, 0, l); + snprintf(buf, l, ctx->chunk + pre); + ctx->handler->handleError( ctx->userData, + "*JSON Parser Error\n - char = %c\n " + "- index = %d\n - near => %s\n - %s", + ctx->chunk[ctx->index], ctx->index, buf, VA_BUF ); + } + JSON_STATE_SET(ctx, JSON_STATE_IS_INVALID); + return -1; +} + + +int _jsonParserHandleUnicode( jsonParserContext* ctx ) { + + /* collect as many of the utf characters as we can in this chunk */ + JSON_CACHE_DATA(ctx, ctx->utfbuf, 4); + + /* we ran off the end of the chunk */ + if( ctx->utfbuf->n_used < 4 ) { + JSON_STATE_SET(ctx, JSON_STATE_IN_UTF); + return 1; + } + + ctx->index--; /* push it back to index of the final utf char */ + + /* ----------------------------------------------------------------------- */ + /* We have all of the escaped unicode data. Write it to the buffer */ + /* The following chunk is used with permission from + * json-c http://oss.metaparadigm.com/json-c/ + */ + #define hexdigit(x) ( ((x) <= '9') ? (x) - '0' : ((x) & 7) + 9) + unsigned char utf_out[4]; + memset(utf_out,0,4); + char* buf = ctx->utfbuf->buf; + + unsigned int ucs_char = + (hexdigit(buf[0] ) << 12) + + (hexdigit(buf[1]) << 8) + + (hexdigit(buf[2]) << 4) + + hexdigit(buf[3]); + + if (ucs_char < 0x80) { + utf_out[0] = ucs_char; + OSRF_BUFFER_ADD(ctx->buffer, (char*)utf_out); + + } else if (ucs_char < 0x800) { + utf_out[0] = 0xc0 | (ucs_char >> 6); + utf_out[1] = 0x80 | (ucs_char & 0x3f); + OSRF_BUFFER_ADD(ctx->buffer, (char*)utf_out); + + } else { + utf_out[0] = 0xe0 | (ucs_char >> 12); + utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f); + utf_out[2] = 0x80 | (ucs_char & 0x3f); + OSRF_BUFFER_ADD(ctx->buffer, (char*)utf_out); + } + /* ----------------------------------------------------------------------- */ + /* ----------------------------------------------------------------------- */ + + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_UTF); + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_ESCAPE); + buffer_reset(ctx->utfbuf); + return 0; +} + + + +/* type : 0=null, 1=true, 2=false */ +int _jsonParserHandleMatch( jsonParserContext* ctx, int type ) { + + switch(type) { + + case 0: /* JSON null */ + + /* first see if we have it all first */ + if( ctx->chunksize > (ctx->index + 3) ) { + if( strncasecmp(ctx->chunk + ctx->index, "null", 4) ) + return _jsonParserError(ctx, "Invalid JSON 'null' sequence"); + if( ctx->handler->handleNull ) + ctx->handler->handleNull(ctx->userData); + ctx->index += 4; + break; + } + + JSON_CACHE_DATA(ctx, ctx->buffer, 4); + if( ctx->buffer->n_used < 4 ) { + JSON_STATE_SET(ctx, JSON_STATE_IN_NULL); + return 1; + } + + if( strncasecmp(ctx->buffer->buf, "null", 4) ) + return _jsonParserError(ctx, "Invalid JSON 'null' sequence"); + if( ctx->handler->handleNull ) + ctx->handler->handleNull(ctx->userData); + break; + + case 1: /* JSON true */ + + /* see if we have it all first */ + if( ctx->chunksize > (ctx->index + 3) ) { + if( strncasecmp(ctx->chunk + ctx->index, "true", 4) ) + return _jsonParserError(ctx, "Invalid JSON 'true' sequence"); + if( ctx->handler->handleBool ) + ctx->handler->handleBool(ctx->userData, 1); + ctx->index += 4; + break; + } + + JSON_CACHE_DATA(ctx, ctx->buffer, 4); + if( ctx->buffer->n_used < 4 ) { + JSON_STATE_SET(ctx, JSON_STATE_IN_TRUE); + return 1; + } + if( strncasecmp( ctx->buffer->buf, "true", 4 ) ) { + return _jsonParserError(ctx, "Invalid JSON 'true' sequence"); + } + if( ctx->handler->handleBool ) + ctx->handler->handleBool(ctx->userData, 1); + break; + + case 2: /* JSON false */ + + /* see if we have it all first */ + if( ctx->chunksize > (ctx->index + 4) ) { + if( strncasecmp(ctx->chunk + ctx->index, "false", 5) ) + return _jsonParserError(ctx, "Invalid JSON 'false' sequence"); + if( ctx->handler->handleBool ) + ctx->handler->handleBool(ctx->userData, 0); + ctx->index += 5; + break; + } + + JSON_CACHE_DATA(ctx, ctx->buffer, 5); + if( ctx->buffer->n_used < 5 ) { + JSON_STATE_SET(ctx, JSON_STATE_IN_FALSE); + return 1; + } + if( strncasecmp( ctx->buffer->buf, "false", 5 ) ) + return _jsonParserError(ctx, "Invalid JSON 'false' sequence"); + if( ctx->handler->handleBool ) + ctx->handler->handleBool(ctx->userData, 0); + break; + + default: + fprintf(stderr, "Invalid type flag\n"); + return -1; + + } + + ctx->index--; /* set it back to the index of the final sequence character */ + buffer_reset(ctx->buffer); + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_NULL); + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_TRUE); + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_FALSE); + + return 0; +} + + +int _jsonParserHandleString( jsonParserContext* ctx ) { + + char c = ctx->chunk[ctx->index]; + + if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_ESCAPE) ) { + + if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_UTF) ) { + + return _jsonParserHandleUnicode( ctx ); + + } else { + + switch(c) { + + /* handle all of the escape chars */ + case '\\': OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\\' ); break; + case '"' : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\"' ); break; + case 't' : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\t' ); break; + case 'b' : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\b' ); break; + case 'f' : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\f' ); break; + case 'r' : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\r' ); break; + case 'n' : OSRF_BUFFER_ADD_CHAR( ctx->buffer, '\n' ); break; + case 'u' : + ctx->index++; /* progress to the first utf char */ + return _jsonParserHandleUnicode( ctx ); + default : OSRF_BUFFER_ADD_CHAR( ctx->buffer, c ); + } + } + + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_ESCAPE); + return 0; + + } else { + + switch(c) { + + case '"' : /* this string is ending */ + if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_KEY) ) { + + /* object key */ + if(ctx->handler->handleObjectKey) { + ctx->handler->handleObjectKey( + ctx->userData, ctx->buffer->buf); + } + + } else { /* regular json string */ + + if(ctx->handler->handleString) { + ctx->handler->handleString( + ctx->userData, ctx->buffer->buf ); + } + + } + + buffer_reset(ctx->buffer); /* flush the buffer and states */ + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_STRING); + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_KEY); + break; + + case '\\' : JSON_STATE_SET(ctx, JSON_STATE_IN_ESCAPE); break; + default : OSRF_BUFFER_ADD_CHAR( ctx->buffer, c ); + } + } + return 0; +} + + +int _jsonParserHandleNumber( jsonParserContext* ctx ) { + char c = ctx->chunk[ctx->index]; + + do { + OSRF_BUFFER_ADD_CHAR(ctx->buffer, c); + c = ctx->chunk[++(ctx->index)]; + } while( strchr(JSON_NUMBER_CHARS, c) && ctx->index < ctx->chunksize ); + + /* if we're run off the end of the chunk and we're not parsing the last chunk, + * save the number and the state */ + if( ctx->index >= ctx->chunksize && + ! JSON_PARSE_FLAG_CHECK(ctx, JSON_PARSE_LAST_CHUNK) ) { + JSON_STATE_SET(ctx, JSON_STATE_IN_NUMBER); + return 1; + } + + /* make me more strict */ + char* err = NULL; + long double d = strtod(ctx->buffer->buf, &err); + if(err && err[0] != '\0') + return _jsonParserError(ctx, "Invalid number sequence"); + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_NUMBER); + buffer_reset(ctx->buffer); + if(ctx->handler->handleNumber) + ctx->handler->handleNumber( ctx->userData, d ); + ctx->index--; /* scooch back to the first non-digit number */ + return 0; +} + + + + +int jsonParseChunk( jsonParserContext* ctx, char* data, int datalen, int flags ) { + + if( !( ctx && ctx->handler && data && datalen > 0 )) return -1; + ctx->chunksize = datalen; + ctx->chunk = data; + ctx->flags = flags; + char c; + + if( JSON_STATE_CHECK(ctx, JSON_STATE_IS_INVALID) ) + return _jsonParserError( ctx, "JSON Parser cannot continue after an error" ); + + if( JSON_STATE_CHECK(ctx, JSON_STATE_IS_DONE) ) + return _jsonParserError( ctx, "Extra content at end of JSON data" ); + + for( ctx->index = 0; (ctx->index < ctx->chunksize) && + (c = ctx->chunk[ctx->index]); ctx->index++ ) { + + /* middle of parsing a string */ + if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_STRING)) { + if( _jsonParserHandleString(ctx) == -1 ) + return -1; + continue; + } + + /* middle of parsing a number */ + if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_NUMBER) ) { + if( _jsonParserHandleNumber(ctx) == -1 ) + return -1; + continue; + } + + +#ifdef JSON_IGNORE_COMMENTS + /* we just saw a bare '/' character */ + if( JSON_STATE_CHECK(ctx, JSON_STATE_START_COMMENT) ) { + if(c == '*') { + JSON_STATE_REMOVE(ctx, JSON_STATE_START_COMMENT); + JSON_STATE_SET(ctx, JSON_STATE_IN_COMMENT); + continue; + } else { + return _jsonParserError( ctx, "Invalid comment initializer" ); + } + } + + /* we're currently in the middle of a comment block */ + if( JSON_STATE_CHECK(ctx, JSON_STATE_IN_COMMENT) ) { + if(c == '*') { + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_COMMENT); + JSON_STATE_SET(ctx, JSON_STATE_END_COMMENT); + continue; + } else { + continue; + } + } + + /* we're in a comment, and we just saw a '*' character */ + if( JSON_STATE_CHECK(ctx, JSON_STATE_END_COMMENT) ) { + if( c == '/' ) { /* comment is finished */ + JSON_STATE_REMOVE(ctx, JSON_STATE_END_COMMENT); + continue; + } else { + /* looks like this isn't the end of the comment after all */ + JSON_STATE_SET(ctx, JSON_STATE_IN_COMMENT); + JSON_STATE_REMOVE(ctx, JSON_STATE_END_COMMENT); + } + } +#endif + + /* if we're in the middle of parsing a null/true/false sequence */ + if( JSON_STATE_CHECK(ctx, (JSON_STATE_IN_NULL | + JSON_STATE_IN_TRUE | JSON_STATE_IN_FALSE)) ) { + + int type = (JSON_STATE_CHECK(ctx, JSON_STATE_IN_NULL)) ? 0 : + (JSON_STATE_CHECK(ctx, JSON_STATE_IN_TRUE)) ? 1 : 2; + + if( _jsonParserHandleMatch( ctx, type ) == -1 ) + return -1; + continue; + } + + JSON_EAT_WS(ctx); + + /* handle all of the top level characters */ + switch(c) { + + case '{' : /* starting an object */ + if( ctx->handler->handleStartObject) + ctx->handler->handleStartObject( ctx->userData ); + JSON_STATE_PUSH(ctx, JSON_STATE_IN_OBJECT); + JSON_STATE_SET(ctx, JSON_STATE_IN_KEY); + break; + + case '}' : /* ending an object */ + if( ctx->handler->handleEndObject) + ctx->handler->handleEndObject( ctx->userData ); + JSON_STATE_POP(ctx); + if( JSON_STATE_PEEK(ctx) == NULL ) + JSON_STATE_SET(ctx, JSON_STATE_IS_DONE); + break; + + case '[' : /* starting an array */ + if( ctx->handler->handleStartArray ) + ctx->handler->handleStartArray( ctx->userData ); + JSON_STATE_PUSH(ctx, JSON_STATE_IN_ARRAY); + break; + + case ']': /* ending an array */ + if( ctx->handler->handleEndArray ) + ctx->handler->handleEndArray( ctx->userData ); + JSON_STATE_POP(ctx); + if( JSON_STATE_PEEK(ctx) == NULL ) + JSON_STATE_SET(ctx, JSON_STATE_IS_DONE); + break; + + case ':' : /* done with the object key */ + JSON_STATE_REMOVE(ctx, JSON_STATE_IN_KEY); + break; + + case ',' : /* after object or array item */ + if( JSON_STATE_CHECK_STACK(ctx, JSON_STATE_IN_OBJECT) ) + JSON_STATE_SET(ctx, JSON_STATE_IN_KEY); + break; + + case 'n' : + case 'N' : /* null */ + if( _jsonParserHandleMatch( ctx, 0 ) == -1) + return -1; + break; + + case 't' : + case 'T' : + if( _jsonParserHandleMatch( ctx, 1 ) == -1 ) + return -1; + break; + + case 'f' : + case 'F' : + if( _jsonParserHandleMatch( ctx, 2 ) == -1) + return -1; + break; + + case '"' : + JSON_STATE_SET(ctx, JSON_STATE_IN_STRING); + break; + +#ifdef JSON_IGNORE_COMMENTS + case '/' : + JSON_STATE_SET(ctx, JSON_STATE_START_COMMENT); + break; +#endif + + default: + if( strchr(JSON_NUMBER_CHARS, c) ) { + if( _jsonParserHandleNumber( ctx ) == -1 ) + return -1; + } else { + return _jsonParserError( ctx, "Invalid Token" ); + } + } + } + + return 0; +} + + +jsonInternalParser* _jsonNewInternalParser() { + jsonInternalParser* p; + OSRF_MALLOC(p, sizeof(jsonInternalParser)); + p->ctx = jsonNewParser( jsonInternalParserHandler, p ); + p->obj = NULL; + p->lastkey = NULL; + return p; +} + +void _jsonInternalParserFree(jsonInternalParser* p) { + if(!p) return; + jsonParserFree(p->ctx); + free(p->lastkey); + free(p); +} + +static jsonObject* _jsonParseStringImpl(char* str, void (*errorHandler) (const char*) ) { + jsonInternalParser* parser = _jsonNewInternalParser(); + parser->handleError = errorHandler; + jsonParseChunk( parser->ctx, str, strlen(str), JSON_PARSE_LAST_CHUNK ); + jsonObject* obj = parser->obj; + _jsonInternalParserFree(parser); + return obj; +} + +jsonObject* jsonParseStringHandleError( + void (*errorHandler) (const char*), char* str, ... ) { + if(!str) return NULL; + VA_LIST_TO_STRING(str); + return _jsonParseStringImpl(VA_BUF, errorHandler); +} + +jsonObject* jsonParseString( char* str ) { + if(!str) return NULL; + jsonObject* obj = _jsonParseStringImpl(str, NULL); + jsonObject* obj2 = jsonObjectDecodeClass(obj); + jsonObjectFree(obj); + return obj2; +} + +jsonObject* jsonParseStringRaw( char* str ) { + if(!str) return NULL; + return _jsonParseStringImpl(str, NULL); +} + +jsonObject* jsonParseStringFmt( char* str, ... ) { + if(!str) return NULL; + VA_LIST_TO_STRING(str); + return _jsonParseStringImpl(VA_BUF, NULL); +} + + +#define JSON_SHOVE_ITEM(ctx,type) \ + jsonInternalParser* p = (jsonInternalParser*) ctx;\ + _jsonInsertParserItem(p, jsonNewObjectType(type)); + +void _jsonHandleStartObject(void* ctx) { JSON_SHOVE_ITEM(ctx, JSON_HASH); } +void _jsonHandleStartArray(void* ctx) { JSON_SHOVE_ITEM(ctx, JSON_ARRAY); } +void _jsonHandleNull(void* ctx) { JSON_SHOVE_ITEM(ctx, JSON_NULL); } + +void _jsonHandleObjectKey(void* ctx, char* key) { + jsonInternalParser* p = (jsonInternalParser*) ctx; + free(p->lastkey); + p->lastkey = strdup(key); +} + +void _jsonHandleEndObject(void* ctx) { + jsonInternalParser* p = (jsonInternalParser*) ctx; + p->current = p->current->parent; +} + +void _jsonHandleEndArray(void* ctx) { + jsonInternalParser* p = (jsonInternalParser*) ctx; + p->current = p->current->parent; +} + +void _jsonHandleString(void* ctx, char* string) { + jsonInternalParser* p = (jsonInternalParser*) ctx; + _jsonInsertParserItem(p, jsonNewObject(string)); +} + +void _jsonHandleBool(void* ctx, int boolval) { + jsonInternalParser* p = (jsonInternalParser*) ctx; + jsonObject* obj = jsonNewObjectType(JSON_BOOL); + obj->value.b = boolval; + _jsonInsertParserItem(p, obj); +} + +void _jsonHandleNumber(void* ctx, long double num) { + jsonInternalParser* p = (jsonInternalParser*) ctx; + _jsonInsertParserItem(p, jsonNewNumberObject(num)); +} + +void _jsonHandleError(void* ctx, char* str, ...) { + jsonInternalParser* p = (jsonInternalParser*) ctx; + VA_LIST_TO_STRING(str); + + if( p->handleError ) + p->handleError(VA_BUF); + else + if( jsonClientErrorCallback ) + jsonClientErrorCallback(VA_BUF); + + else fprintf(stderr, "%s\n", VA_BUF); + jsonObjectFree(p->obj); + p->obj = NULL; +} + + +void _jsonInsertParserItem( jsonInternalParser* p, jsonObject* newo ) { + + if( !p->obj ) { + + /* new parser, set the new object to our object */ + p->obj = p->current = newo; + + } else { + + /* insert the new object into the current container object */ + switch(p->current->type) { + case JSON_HASH : jsonObjectSetKey(p->current, p->lastkey, newo); break; + case JSON_ARRAY: jsonObjectPush(p->current, newo); break; + default: fprintf(stderr, "%s:%d -> how?\n", JSON_LOG_MARK); + } + + /* if the new object is a container object, make it our current container */ + if( newo->type == JSON_ARRAY || newo->type == JSON_HASH ) + p->current = newo; + } +} + + diff --git a/src/libstack/osrf_json_tools.c b/src/libstack/osrf_json_tools.c new file mode 100644 index 0000000..6144e8c --- /dev/null +++ b/src/libstack/osrf_json_tools.c @@ -0,0 +1,437 @@ +/* +Copyright (C) 2006 Georgia Public Library Service +Bill Erickson + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "osrf_json.h" + +jsonObject* _jsonObjectEncodeClass( jsonObject* obj, int ignoreClass ); + + +jsonObject* jsonObjectFindPath( const jsonObject* obj, char* path, ...); +jsonObject* _jsonObjectFindPathRecurse(const jsonObject* obj, char* root, char* path); +jsonObject* __jsonObjectFindPathRecurse(const jsonObject* obj, char* root); + + +static char* __tabs(int count) { + growing_buffer* buf = buffer_init(24); + int i; + for(i=0;itype == JSON_HASH ) { + + /* are we a special class object? */ + if( (classObj = jsonObjectGetKey( obj, JSON_CLASS_KEY )) ) { + + /* do we have a payload */ + if( (payloadObj = jsonObjectGetKey( obj, JSON_DATA_KEY )) ) { + newObj = jsonObjectDecodeClass( payloadObj ); + jsonObjectSetClass( newObj, jsonObjectGetString(classObj) ); + + } else { /* class is defined but there is no payload */ + return NULL; + } + + } else { /* we're a regular hash */ + + jsonObjectIterator* itr = jsonNewObjectIterator(obj); + jsonObject* tmp; + newObj = jsonNewObjectType(JSON_HASH); + while( (tmp = jsonObjectIteratorNext(itr)) ) { + jsonObject* o = jsonObjectDecodeClass(tmp); + jsonObjectSetKey( newObj, itr->key, o ); + } + jsonObjectIteratorFree(itr); + } + + } else { + + if( obj->type == JSON_ARRAY ) { /* we're an array */ + newObj = jsonNewObjectType(JSON_ARRAY); + for( i = 0; i != obj->size; i++ ) { + jsonObject* tmp = jsonObjectDecodeClass(jsonObjectGetIndex( obj, i ) ); + jsonObjectSetIndex( newObj, i, tmp ); + } + + } else { /* not an aggregate type */ + newObj = jsonObjectClone(obj); + } + } + + return newObj; +} + +jsonObject* jsonObjectEncodeClass( jsonObject* obj ) { + return _jsonObjectEncodeClass( obj, 0 ); +} + +jsonObject* _jsonObjectEncodeClass( jsonObject* obj, int ignoreClass ) { + + //if(!obj) return NULL; + if(!obj) return jsonNewObject(NULL); + jsonObject* newObj = NULL; + + if( obj->classname && ! ignoreClass ) { + newObj = jsonNewObjectType(JSON_HASH); + + jsonObjectSetKey( newObj, + JSON_CLASS_KEY, jsonNewObject(obj->classname) ); + + jsonObjectSetKey( newObj, + JSON_DATA_KEY, _jsonObjectEncodeClass(obj, 1)); + + } else if( obj->type == JSON_HASH ) { + + jsonObjectIterator* itr = jsonNewObjectIterator(obj); + jsonObject* tmp; + newObj = jsonNewObjectType(JSON_HASH); + + while( (tmp = jsonObjectIteratorNext(itr)) ) { + jsonObjectSetKey( newObj, itr->key, + _jsonObjectEncodeClass(tmp, 0)); + } + jsonObjectIteratorFree(itr); + + } else if( obj->type == JSON_ARRAY ) { + + newObj = jsonNewObjectType(JSON_ARRAY); + int i; + for( i = 0; i != obj->size; i++ ) { + jsonObjectSetIndex( newObj, i, + _jsonObjectEncodeClass(jsonObjectGetIndex( obj, i ), 0 )); + } + + } else { + newObj = jsonObjectClone(obj); + } + + return newObj; +} + + + + +static char* _escape_xml (char*); +static int _recurse_jsonObjectToXML(jsonObject*, growing_buffer*); + +char* jsonObjectToXML(jsonObject* obj) { + + growing_buffer * res_xml; + char * output; + + res_xml = buffer_init(1024); + + if (!obj) + return strdup(""); + + _recurse_jsonObjectToXML( obj, res_xml ); + output = buffer_data(res_xml); + + buffer_free(res_xml); + + return output; + +} + +int _recurse_jsonObjectToXML(jsonObject* obj, growing_buffer* res_xml) { + + char * hint = NULL; + char * bool_val = NULL; + int i = 0; + + if (obj->classname) + hint = strdup(obj->classname); + + if(obj->type == JSON_NULL) { + + if (hint) + buffer_fadd(res_xml, "",hint); + else + buffer_add(res_xml, ""); + + } else if(obj->type == JSON_BOOL) { + + if (obj->value.b) + bool_val = strdup("true"); + else + bool_val = strdup("false"); + + if (hint) + buffer_fadd(res_xml, "", bool_val, hint); + else + buffer_fadd(res_xml, "", bool_val); + + free(bool_val); + + } else if (obj->type == JSON_STRING) { + if (hint) { + char * t = _escape_xml(jsonObjectGetString(obj)); + buffer_fadd(res_xml,"%s", hint, t); + free(t); + } else { + char * t = _escape_xml(jsonObjectGetString(obj)); + buffer_fadd(res_xml,"%s", t); + free(t); + } + + } else if(obj->type == JSON_NUMBER) { + double x = jsonObjectGetNumber(obj); + if (hint) { + if (x == (int)x) + buffer_fadd(res_xml,"%d", hint, (int)x); + else + buffer_fadd(res_xml,"%lf", hint, x); + } else { + if (x == (int)x) + buffer_fadd(res_xml,"%d", (int)x); + else + buffer_fadd(res_xml,"%lf", x); + } + + } else if (obj->type == JSON_ARRAY) { + + if (hint) + buffer_fadd(res_xml,"", hint); + else + buffer_add(res_xml,""); + + for ( i = 0; i!= obj->size; i++ ) + _recurse_jsonObjectToXML(jsonObjectGetIndex(obj,i), res_xml); + + buffer_add(res_xml,""); + + } else if (obj->type == JSON_HASH) { + + if (hint) + buffer_fadd(res_xml,"", hint); + else + buffer_add(res_xml,""); + + jsonObjectIterator* itr = jsonNewObjectIterator(obj); + jsonObject* tmp; + while( (tmp = jsonObjectIteratorNext(itr)) ) { + buffer_fadd(res_xml,"",itr->key); + _recurse_jsonObjectToXML(tmp, res_xml); + buffer_add(res_xml,""); + } + jsonObjectIteratorFree(itr); + + buffer_add(res_xml,""); + } + + if (hint) + free(hint); + + return 1; +} + +char* _escape_xml (char* text) { + char* out; + growing_buffer* b = buffer_init(256); + int len = strlen(text); + int i; + for (i = 0; i < len; i++) { + if (text[i] == '&') + buffer_add(b,"&"); + else if (text[i] == '<') + buffer_add(b,"<"); + else if (text[i] == '>') + buffer_add(b,">"); + else + buffer_add_char(b,text[i]); + } + out = buffer_data(b); + buffer_free(b); + return out; +} + + +jsonObject* jsonParseFile( char* filename ) { + if(!filename) return NULL; + char* data = file_to_string(filename); + jsonObject* o = jsonParseString(data); + free(data); + return o; +} + + + +jsonObject* jsonObjectFindPath( const jsonObject* obj, char* format, ...) { + if(!obj || !format || strlen(format) < 1) return NULL; + + VA_LIST_TO_STRING(format); + char* buf = VA_BUF; + char* token = NULL; + char* t = buf; + char* tt; /* strtok storage */ + + /* copy the path before strtok_r destroys it */ + char* pathcopy = strdup(buf); + + /* grab the root of the path */ + token = strtok_r(t, "/", &tt); + if(!token) return NULL; + + /* special case where path starts with // (start anywhere) */ + if(strlen(pathcopy) > 2 && pathcopy[0] == '/' && pathcopy[1] == '/') { + jsonObject* it = _jsonObjectFindPathRecurse(obj, token, pathcopy + 1); + free(pathcopy); + return it; + } + + free(pathcopy); + + t = NULL; + do { + obj = jsonObjectGetKey(obj, token); + } while( (token = strtok_r(NULL, "/", &tt)) && obj); + + return jsonObjectClone(obj); +} + +/* --------------------------------------------------------------- */ + + + +jsonObject* _jsonObjectFindPathRecurse(const jsonObject* obj, char* root, char* path) { + + if(!obj || ! root || !path) return NULL; + + /* collect all of the potential objects */ + jsonObject* arr = __jsonObjectFindPathRecurse(obj, root); + + /* container for fully matching objects */ + jsonObject* newarr = jsonParseString("[]"); + int i; + + /* path is just /root or /root/ */ + if( strlen(root) + 2 >= strlen(path) ) { + return arr; + + } else { + + /* gather all of the sub-objects that match the full path */ + for( i = 0; i < arr->size; i++ ) { + jsonObject* a = jsonObjectGetIndex(arr, i); + jsonObject* thing = jsonObjectFindPath(a , path + strlen(root) + 1); + + if(thing) { //jsonObjectPush(newarr, thing); + if(thing->type == JSON_ARRAY) { + int i; + for( i = 0; i != thing->size; i++ ) + jsonObjectPush(newarr, jsonObjectClone(jsonObjectGetIndex(thing,i))); + jsonObjectFree(thing); + + } else { + jsonObjectPush(newarr, thing); + } + } + } + } + + jsonObjectFree(arr); + return newarr; +} + +jsonObject* __jsonObjectFindPathRecurse(const jsonObject* obj, char* root) { + + jsonObject* arr = jsonParseString("[]"); + if(!obj) return arr; + + int i; + + /* if the current object has a node that matches, add it */ + + jsonObject* o = jsonObjectGetKey(obj, root); + if(o) jsonObjectPush( arr, jsonObjectClone(o) ); + + jsonObject* tmp = NULL; + jsonObject* childarr; + jsonObjectIterator* itr = jsonNewObjectIterator(obj); + + /* recurse through the children and find all potential nodes */ + while( (tmp = jsonObjectIteratorNext(itr)) ) { + childarr = __jsonObjectFindPathRecurse(tmp, root); + if(childarr && childarr->size > 0) { + for( i = 0; i!= childarr->size; i++ ) { + jsonObjectPush( arr, jsonObjectClone(jsonObjectGetIndex(childarr, i)) ); + } + } + jsonObjectFree(childarr); + } + + jsonObjectIteratorFree(itr); + + return arr; +} + + diff --git a/src/libstack/osrf_json_utils.h b/src/libstack/osrf_json_utils.h new file mode 100644 index 0000000..76af666 --- /dev/null +++ b/src/libstack/osrf_json_utils.h @@ -0,0 +1,131 @@ +/* +Copyright (C) 2006 Georgia Public Library Service +Bill Erickson + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + + +/* ----------------------------------------------------------------------- */ +/* Clients need not include this file. These are internal utilities only */ +/* ----------------------------------------------------------------------- */ + +#define JSON_EAT_WS(ctx) \ + while( ctx->index < ctx->chunksize ) { \ + if(!isspace(ctx->chunk[ctx->index])) break; \ + ctx->index++; \ + } \ + if( ctx->index >= ctx->chunksize ) return 0; \ + c = ctx->chunk[ctx->index]; + +#define JSON_CACHE_DATA(ctx, buf, size) \ + while( (buf->n_used < size) && (ctx->index < ctx->chunksize) ) \ + buffer_add_char(buf, ctx->chunk[ctx->index++]); + +#define JSON_LOG_MARK __FILE__,__LINE__ + +#define JSON_NUMBER_CHARS "0123456789.+-e" + + +/* cleans up an object if it is morphing another object, also + * verifies that the appropriate storage container exists where appropriate */ +#define JSON_INIT_CLEAR(_obj_, newtype) \ + if( _obj_->type == JSON_HASH && newtype != JSON_HASH ) { \ + osrfHashFree(_obj_->value.h); \ + _obj_->value.h = NULL; \ + } else if( _obj_->type == JSON_ARRAY && newtype != JSON_ARRAY ) { \ + osrfListFree(_obj_->value.l); \ + _obj_->value.l = NULL; \ + } else if( _obj_->type == JSON_STRING && newtype != JSON_STRING ) { \ + free(_obj_->value.s); \ + _obj_->value.s = NULL; \ + } \ + _obj_->type = newtype;\ + if( newtype == JSON_HASH && _obj_->value.h == NULL ) { \ + _obj_->value.h = osrfNewHash(); \ + _obj_->value.h->freeItem = _jsonFreeHashItem; \ + } else if( newtype == JSON_ARRAY && _obj_->value.l == NULL ) { \ + _obj_->value.l = osrfNewList(); \ + _obj_->value.l->freeItem = _jsonFreeListItem;\ + } \ + + +/** + * These are the callbacks through which the top level parser + * builds objects via the push parser + */ +void _jsonHandleStartObject(void*); +void _jsonHandleObjectKey(void*, char* key); +void _jsonHandleEndObject(void*); +void _jsonHandleStartArray(void*); +void _jsonHandleEndArray(void*); +void _jsonHandleNull(void*); +void _jsonHandleString(void*, char* string); +void _jsonHandleBool(void*, int boolval); +void _jsonHandleNumber(void*, long double num); +void _jsonHandleError(void*, char* str, ...); + +struct jsonInternalParserStruct { + jsonParserContext* ctx; + jsonObject* obj; + jsonObject* current; + char* lastkey; + void (*handleError) (const char*); +}; +typedef struct jsonInternalParserStruct jsonInternalParser; + +jsonInternalParser* _jsonNewInternalParser(); +void _jsonInternalParserFree(jsonInternalParser* p); + +/** + * Calls the defined error handler with the given error message. + * @return -1 + */ +int _jsonParserError( jsonParserContext* ctx, char* err, ... ); + + +/** + * + * @return 0 on continue, 1 if it goes past the end of the string, -1 on error + */ +int _jsonParserHandleUnicode( jsonParserContext* ctx ); + + +/** + * @param type 0 for null, 1 for true, 2 for false + * @return 0 on continue, 1 if it goes past the end of the string, -1 on error + */ +int _jsonParserHandleMatch( jsonParserContext* ctx, int type ); + +/** + * @return 0 on continue, 1 on end of chunk, -1 on error + */ +int _jsonParserHandleString( jsonParserContext* ctx ); + +/** + * @return 0 on continue, 1 on end of chunk, -1 on error + */ +int _jsonParserHandleNumber( jsonParserContext* ctx ); + + +void _jsonInsertParserItem( jsonInternalParser* p, jsonObject* newo ); + + +/* Utility method. finds any object in the tree that matches the path. + Use this for finding paths that start with '//' */ +jsonObject* _jsonObjectFindPathRecurse( const jsonObject* o, char* root, char* path ); + + +/* returns a list of object whose key is 'root'. These are used as + potential objects when doing a // search */ +jsonObject* __jsonObjectFindPathRecurse( const jsonObject* o, char* root ); + + -- 2.11.0