2 Copyright (C) 2005 Georgia Public Library Service
3 Bill Erickson <highfalutin@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 "json_parser.h"
19 /* keep a copy of the length of the current json string so we don't
20 * have to calculate it in each function
22 int current_strlen; /* XXX need to move this into the function params for thread support */
25 jsonObject* jsonParseString( char* string, ... ) {
26 VA_LIST_TO_STRING(string);
27 return json_parse_string( VA_BUF );
30 //jsonObject* (*jsonParseString) (char* str) = &_jsonParseString;
32 jsonObject* json_parse_string(char* string) {
34 if(string == NULL) return NULL;
36 current_strlen = strlen(string);
38 if(current_strlen == 0)
41 unsigned long index = 0;
43 json_eat_ws(string, &index, 1); /* remove leading whitespace */
44 if(index == current_strlen) return NULL;
46 jsonObject* obj = jsonNewObject(NULL);
48 int status = _json_parse_string(string, &index, obj);
49 if(!status) return obj;
60 int _json_parse_string(char* string, unsigned long* index, jsonObject* obj) {
61 if( !string || !index || *index >= current_strlen) return -2;
63 int status = 0; /* return code from parsing routines */
64 char* classname = NULL; /* object class hint */
65 json_eat_ws(string, index, 1); /* remove leading whitespace */
67 char c = string[*index];
69 /* remove any leading comments */
73 (*index)++; /* move to second comment char */
74 status = json_eat_comment(string, index, &classname, 1);
75 if(status) return status;
77 json_eat_ws(string, index, 1);
84 json_eat_ws(string, index, 1); /* remove leading whitespace */
86 if(*index >= current_strlen)
94 status = json_parse_json_string(string, index, obj);
100 status = json_parse_json_array(string, index, obj);
106 status = json_parse_json_object(string, index, obj);
112 status = json_parse_json_null(string, index, obj);
121 status = json_parse_json_bool(string, index, obj);
125 if(is_number(c) || c == '.' || c == '-') { /* are we a number? */
126 status = json_parse_json_number(string, index, obj);
127 if(status) return status;
132 /* we should never get here */
133 return json_handle_error(string, index, "_json_parse_string() final switch clause");
136 if(status) return status;
138 json_eat_ws(string, index, 1);
140 if( *index < current_strlen ) {
141 /* remove any trailing comments */
145 status = json_eat_comment(string, index, NULL, 0);
146 if(status) return status;
151 jsonObjectSetClass(obj, classname);
159 int json_parse_json_null(char* string, unsigned long* index, jsonObject* obj) {
161 if(*index >= (current_strlen - 3)) {
162 return json_handle_error(string, index,
163 "_parse_json_string(): invalid null" );
166 if(!strncasecmp(string + (*index), "null", 4)) {
168 obj->type = JSON_NULL;
171 return json_handle_error(string, index,
172 "_parse_json_string(): invalid null" );
176 /* should be at the first character of the bool at this point */
177 int json_parse_json_bool(char* string, unsigned long* index, jsonObject* obj) {
178 if( ! string || ! obj || *index >= current_strlen ) return -1;
180 char* ret = "json_parse_json_bool(): truncated bool";
182 if( *index >= (current_strlen - 5))
183 return json_handle_error(string, index, ret);
185 if(!strncasecmp( string + (*index), "false", 5)) {
188 obj->type = JSON_BOOL;
192 if( *index >= (current_strlen - 4))
193 return json_handle_error(string, index, ret);
195 if(!strncasecmp( string + (*index), "true", 4)) {
198 obj->type = JSON_BOOL;
202 return json_handle_error(string, index, ret);
206 /* expecting the first character of the number */
207 int json_parse_json_number(char* string, unsigned long* index, jsonObject* obj) {
208 if( ! string || ! obj || *index >= current_strlen ) return -1;
210 growing_buffer* buf = buffer_init(64);
211 char c = string[*index];
216 /* negative number? */
217 if(c == '-') { buffer_add(buf, "-"); (*index)++; }
221 while(*index < current_strlen) {
224 buffer_add_char(buf, c);
227 else if( c == '.' ) {
230 return json_handle_error(string, index,
231 "json_parse_json_number(): malformed json number");
234 buffer_add_char(buf, c);
244 obj->type = JSON_NUMBER;
245 obj->value.n = strtod(buf->buf, NULL);
250 /* index should point to the character directly following the '['. when done
251 * index will point to the character directly following the ']' character
253 int json_parse_json_array(char* string, unsigned long* index, jsonObject* obj) {
255 if( ! string || ! obj || ! index || *index >= current_strlen ) return -1;
258 int in_parse = 0; /* true if this array already contains one item */
259 obj->type = JSON_ARRAY;
263 while(*index < current_strlen) {
265 json_eat_ws(string, index, 1);
267 if(string[*index] == ']') {
274 json_eat_ws(string, index, 1);
275 if(string[*index] != ',') {
276 return json_handle_error(string, index,
277 "json_parse_json_array(): array item not followed by a ','");
280 json_eat_ws(string, index, 1);
283 jsonObject* item = jsonNewObject(NULL);
285 #ifndef STRICT_JSON_READ
286 if(*index < current_strlen) {
287 if(string[*index] == ',' || string[*index] == ']') {
292 if(!set) status = _json_parse_string(string, index, item);
295 status = _json_parse_string(string, index, item);
298 if(status) { jsonObjectFree(item); return status; }
299 jsonObjectPush(obj, item);
305 return json_handle_error(string, index,
306 "json_parse_json_array(): array not closed");
312 /* index should point to the character directly following the '{'. when done
313 * index will point to the character directly following the '}'
315 int json_parse_json_object(char* string, unsigned long* index, jsonObject* obj) {
316 if( ! string || !obj || ! index || *index >= current_strlen ) return -1;
318 obj->type = JSON_HASH;
320 int in_parse = 0; /* true if we've already added one item to this object */
324 while(*index < current_strlen) {
326 json_eat_ws(string, index, 1);
328 if(string[*index] == '}') {
335 if(string[*index] != ',') {
336 return json_handle_error(string, index,
337 "json_parse_json_object(): object missing ',' between elements" );
340 json_eat_ws(string, index, 1);
343 /* first we grab the hash key */
344 jsonObject* key_obj = jsonNewObject(NULL);
345 status = _json_parse_string(string, index, key_obj);
346 if(status) return status;
348 if(key_obj->type != JSON_STRING) {
349 return json_handle_error(string, index,
350 "_json_parse_json_object(): hash key not a string");
353 char* key = key_obj->value.s;
355 json_eat_ws(string, index, 1);
357 if(string[*index] != ':') {
358 return json_handle_error(string, index,
359 "json_parse_json_object(): hash key not followed by ':' character");
364 /* now grab the value object */
365 json_eat_ws(string, index, 1);
366 jsonObject* value_obj = jsonNewObject(NULL);
368 #ifndef STRICT_JSON_READ
369 if(*index < current_strlen) {
370 if(string[*index] == ',' || string[*index] == '}') {
376 status = _json_parse_string(string, index, value_obj);
379 status = _json_parse_string(string, index, value_obj);
382 if(status) return status;
384 /* put the data into the object and continue */
385 jsonObjectSetKey(obj, key, value_obj);
386 jsonObjectFree(key_obj);
392 return json_handle_error(string, index,
393 "json_parse_json_object(): object not closed");
400 /* when done, index will point to the character after the closing quote */
401 int json_parse_json_string(char* string, unsigned long* index, jsonObject* obj) {
402 if( ! string || ! index || *index >= current_strlen ) return -1;
406 growing_buffer* buf = buffer_init(64);
408 while(*index < current_strlen) {
410 char c = string[*index];
416 buffer_add(buf, "\\");
424 buffer_add(buf, "\"");
432 buffer_add(buf,"\t");
435 buffer_add_char(buf, c);
440 buffer_add(buf,"\b");
443 buffer_add_char(buf, c);
448 buffer_add(buf,"\f");
451 buffer_add_char(buf, c);
456 buffer_add(buf,"\r");
459 buffer_add_char(buf, c);
464 buffer_add(buf,"\n");
467 buffer_add_char(buf, c);
474 if(*index >= (current_strlen - 4)) {
476 return json_handle_error(string, index,
477 "json_parse_json_string(): truncated escaped unicode"); }
481 memcpy(buff, string + (*index), 4);
484 /* ----------------------------------------------------------------------- */
485 /* ----------------------------------------------------------------------- */
486 /* The following chunk was borrowed with permission from
487 json-c http://oss.metaparadigm.com/json-c/ */
488 unsigned char utf_out[3];
491 #define hexdigit(x) ( ((x) <= '9') ? (x) - '0' : ((x) & 7) + 9)
493 unsigned int ucs_char =
494 (hexdigit(string[*index] ) << 12) +
495 (hexdigit(string[*index + 1]) << 8) +
496 (hexdigit(string[*index + 2]) << 4) +
497 hexdigit(string[*index + 3]);
499 if (ucs_char < 0x80) {
500 utf_out[0] = ucs_char;
501 buffer_add(buf, utf_out);
503 } else if (ucs_char < 0x800) {
504 utf_out[0] = 0xc0 | (ucs_char >> 6);
505 utf_out[1] = 0x80 | (ucs_char & 0x3f);
506 buffer_add(buf, utf_out);
509 utf_out[0] = 0xe0 | (ucs_char >> 12);
510 utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
511 utf_out[2] = 0x80 | (ucs_char & 0x3f);
512 buffer_add(buf, utf_out);
514 /* ----------------------------------------------------------------------- */
515 /* ----------------------------------------------------------------------- */
522 buffer_add_char(buf, c);
528 buffer_add_char(buf, c);
535 jsonObjectSetString(obj, buf->buf);
541 void json_eat_ws(char* string, unsigned long* index, int eat_all) {
542 if( ! string || ! index ) return;
543 if(*index >= current_strlen)
546 if( eat_all ) { /* removes newlines, etc */
547 while(string[*index] == ' ' ||
548 string[*index] == '\n' ||
549 string[*index] == '\t')
554 while(string[*index] == ' ') (*index)++;
558 /* index should be at the '*' character at the beginning of the comment.
559 * when done, index will point to the first character after the final /
561 int json_eat_comment(char* string, unsigned long* index, char** buffer, int parse_class) {
562 if( ! string || ! index || *index >= current_strlen ) return -1;
565 if(string[*index] != '*' && string[*index] != '/' )
566 return json_handle_error(string, index,
567 "json_eat_comment(): invalid character after /");
569 /* chop out any // style comments */
570 if(string[*index] == '/') {
572 char c = string[*index];
573 while(*index < current_strlen) {
584 int on_star = 0; /* true if we just saw a '*' character */
586 /* we're just past the '*' */
587 if(!parse_class) { /* we're not concerned with class hints */
588 while(*index < current_strlen) {
589 if(string[*index] == '/') {
596 if(string[*index] == '*') on_star = 1;
606 growing_buffer* buf = buffer_init(64);
616 /*--S hint--*/ /* <-- Hints look like this */
619 while(*index < current_strlen) {
620 char c = string[*index];
626 if(third_dash) fourth_dash = 1;
627 else if(in_hint) third_dash = 1;
628 else if(first_dash) second_dash = 1;
634 if(second_dash && !in_hint) {
636 json_eat_ws(string, index, 1);
637 (*index)--; /* this will get incremented at the bottom of the loop */
642 if(second_dash && in_hint) {
643 buffer_add_char(buf, c);
649 if(second_dash && !in_hint) {
651 json_eat_ws(string, index, 1);
652 (*index)--; /* this will get incremented at the bottom of the loop */
657 if(second_dash && in_hint) {
658 buffer_add_char(buf, c);
676 buffer_add_char(buf, c);
683 if( buf->n_used > 0 && buffer)
684 *buffer = buffer_data(buf);
690 int is_number(char c) {
707 int json_handle_error(char* string, unsigned long* index, char* err_msg) {
713 strncpy( buf, string + (*index - 30), 59 );
715 strncpy( buf, string, 59 );
718 "\nError parsing json string at charracter %c "
719 "(code %d) and index %ld\nMsg:\t%s\nNear:\t%s\n\n",
720 string[*index], string[*index], *index, err_msg, buf );
726 jsonObject* jsonParseFile( const char* filename ) {
727 return json_parse_file( filename );
730 jsonObject* json_parse_file(const char* filename) {
731 if(!filename) return NULL;
732 char* data = file_to_string(filename);
733 jsonObject* o = json_parse_string(data);