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 return json_parse_string( string );
29 //jsonObject* (*jsonParseString) (char* str) = &_jsonParseString;
31 jsonObject* json_parse_string(char* string) {
33 if(string == NULL) return NULL;
35 current_strlen = strlen(string);
37 if(current_strlen == 0)
40 unsigned long index = 0;
42 json_eat_ws(string, &index, 1); /* remove leading whitespace */
43 if(index == current_strlen) return NULL;
45 jsonObject* obj = jsonNewObject(NULL);
47 int status = _json_parse_string(string, &index, obj);
48 if(!status) return obj;
59 int _json_parse_string(char* string, unsigned long* index, jsonObject* obj) {
60 if( !string || !index || *index >= current_strlen) return -2;
62 int status = 0; /* return code from parsing routines */
63 char* classname = NULL; /* object class hint */
64 json_eat_ws(string, index, 1); /* remove leading whitespace */
66 char c = string[*index];
68 /* remove any leading comments */
72 (*index)++; /* move to second comment char */
73 status = json_eat_comment(string, index, &classname, 1);
74 if(status) return status;
76 json_eat_ws(string, index, 1);
83 json_eat_ws(string, index, 1); /* remove leading whitespace */
85 if(*index >= current_strlen)
93 status = json_parse_json_string(string, index, obj);
99 status = json_parse_json_array(string, index, obj);
105 status = json_parse_json_object(string, index, obj);
111 status = json_parse_json_null(string, index, obj);
120 status = json_parse_json_bool(string, index, obj);
124 if(is_number(c) || c == '.' || c == '-') { /* are we a number? */
125 status = json_parse_json_number(string, index, obj);
126 if(status) return status;
131 /* we should never get here */
132 return json_handle_error(string, index, "_json_parse_string() final switch clause");
135 if(status) return status;
137 json_eat_ws(string, index, 1);
139 if( *index < current_strlen ) {
140 /* remove any trailing comments */
144 status = json_eat_comment(string, index, NULL, 0);
145 if(status) return status;
150 jsonObjectSetClass(obj, classname);
158 int json_parse_json_null(char* string, unsigned long* index, jsonObject* obj) {
160 if(*index >= (current_strlen - 3)) {
161 return json_handle_error(string, index,
162 "_parse_json_string(): invalid null" );
165 if(!strncasecmp(string + (*index), "null", 4)) {
167 obj->type = JSON_NULL;
170 return json_handle_error(string, index,
171 "_parse_json_string(): invalid null" );
175 /* should be at the first character of the bool at this point */
176 int json_parse_json_bool(char* string, unsigned long* index, jsonObject* obj) {
177 if( ! string || ! obj || *index >= current_strlen ) return -1;
179 char* ret = "json_parse_json_bool(): truncated bool";
181 if( *index >= (current_strlen - 5))
182 return json_handle_error(string, index, ret);
184 if(!strncasecmp( string + (*index), "false", 5)) {
187 obj->type = JSON_BOOL;
191 if( *index >= (current_strlen - 4))
192 return json_handle_error(string, index, ret);
194 if(!strncasecmp( string + (*index), "true", 4)) {
197 obj->type = JSON_BOOL;
201 return json_handle_error(string, index, ret);
205 /* expecting the first character of the number */
206 int json_parse_json_number(char* string, unsigned long* index, jsonObject* obj) {
207 if( ! string || ! obj || *index >= current_strlen ) return -1;
209 growing_buffer* buf = buffer_init(64);
210 char c = string[*index];
215 /* negative number? */
216 if(c == '-') { buffer_add(buf, "-"); (*index)++; }
220 while(*index < current_strlen) {
223 buffer_add_char(buf, c);
226 else if( c == '.' ) {
229 return json_handle_error(string, index,
230 "json_parse_json_number(): malformed json number");
233 buffer_add_char(buf, c);
243 obj->type = JSON_NUMBER;
244 obj->value.n = strtod(buf->buf, NULL);
249 /* index should point to the character directly following the '['. when done
250 * index will point to the character directly following the ']' character
252 int json_parse_json_array(char* string, unsigned long* index, jsonObject* obj) {
254 if( ! string || ! obj || ! index || *index >= current_strlen ) return -1;
257 int in_parse = 0; /* true if this array already contains one item */
258 obj->type = JSON_ARRAY;
262 while(*index < current_strlen) {
264 json_eat_ws(string, index, 1);
266 if(string[*index] == ']') {
273 json_eat_ws(string, index, 1);
274 if(string[*index] != ',') {
275 return json_handle_error(string, index,
276 "json_parse_json_array(): array not followed by a ','");
279 json_eat_ws(string, index, 1);
282 jsonObject* item = jsonNewObject(NULL);
284 #ifndef STRICT_JSON_READ
285 if(*index < current_strlen) {
286 if(string[*index] == ',' || string[*index] == ']') {
291 if(!set) status = _json_parse_string(string, index, item);
294 status = _json_parse_string(string, index, item);
297 if(status) { jsonObjectFree(item); return status; }
298 jsonObjectPush(obj, item);
304 return json_handle_error(string, index,
305 "json_parse_json_array(): array not closed");
311 /* index should point to the character directly following the '{'. when done
312 * index will point to the character directly following the '}'
314 int json_parse_json_object(char* string, unsigned long* index, jsonObject* obj) {
315 if( ! string || !obj || ! index || *index >= current_strlen ) return -1;
317 obj->type = JSON_HASH;
319 int in_parse = 0; /* true if we've already added one item to this object */
323 while(*index < current_strlen) {
325 json_eat_ws(string, index, 1);
327 if(string[*index] == '}') {
334 if(string[*index] != ',') {
335 return json_handle_error(string, index,
336 "json_parse_json_object(): object missing ',' between elements" );
339 json_eat_ws(string, index, 1);
342 /* first we grab the hash key */
343 jsonObject* key_obj = jsonNewObject(NULL);
344 status = _json_parse_string(string, index, key_obj);
345 if(status) return status;
347 if(key_obj->type != JSON_STRING) {
348 return json_handle_error(string, index,
349 "_json_parse_json_object(): hash key not a string");
352 char* key = key_obj->value.s;
354 json_eat_ws(string, index, 1);
356 if(string[*index] != ':') {
357 return json_handle_error(string, index,
358 "json_parse_json_object(): hash key not followed by ':' character");
363 /* now grab the value object */
364 json_eat_ws(string, index, 1);
365 jsonObject* value_obj = jsonNewObject(NULL);
367 #ifndef STRICT_JSON_READ
368 if(*index < current_strlen) {
369 if(string[*index] == ',' || string[*index] == '}') {
375 status = _json_parse_string(string, index, value_obj);
378 status = _json_parse_string(string, index, value_obj);
381 if(status) return status;
383 /* put the data into the object and continue */
384 jsonObjectSetKey(obj, key, value_obj);
385 jsonObjectFree(key_obj);
391 return json_handle_error(string, index,
392 "json_parse_json_object(): object not closed");
399 /* when done, index will point to the character after the closing quote */
400 int json_parse_json_string(char* string, unsigned long* index, jsonObject* obj) {
401 if( ! string || ! index || *index >= current_strlen ) return -1;
405 growing_buffer* buf = buffer_init(64);
407 while(*index < current_strlen) {
409 char c = string[*index];
415 buffer_add(buf, "\\");
423 buffer_add(buf, "\"");
431 buffer_add(buf,"\t");
434 buffer_add_char(buf, c);
439 buffer_add(buf,"\b");
442 buffer_add_char(buf, c);
447 buffer_add(buf,"\f");
450 buffer_add_char(buf, c);
455 buffer_add(buf,"\r");
458 buffer_add_char(buf, c);
463 buffer_add(buf,"\n");
466 buffer_add_char(buf, c);
473 if(*index >= (current_strlen - 4)) {
475 return json_handle_error(string, index,
476 "json_parse_json_string(): truncated escaped unicode"); }
480 memcpy(buff, string + (*index), 4);
483 /* ----------------------------------------------------------------------- */
484 /* ----------------------------------------------------------------------- */
485 /* The following chunk was borrowed with permission from
486 json-c http://oss.metaparadigm.com/json-c/ */
487 unsigned char utf_out[3];
490 #define hexdigit(x) ( ((x) <= '9') ? (x) - '0' : ((x) & 7) + 9)
492 unsigned int ucs_char =
493 (hexdigit(string[*index] ) << 12) +
494 (hexdigit(string[*index + 1]) << 8) +
495 (hexdigit(string[*index + 2]) << 4) +
496 hexdigit(string[*index + 3]);
498 if (ucs_char < 0x80) {
499 utf_out[0] = ucs_char;
500 buffer_add(buf, utf_out);
502 } else if (ucs_char < 0x800) {
503 utf_out[0] = 0xc0 | (ucs_char >> 6);
504 utf_out[1] = 0x80 | (ucs_char & 0x3f);
505 buffer_add(buf, utf_out);
508 utf_out[0] = 0xe0 | (ucs_char >> 12);
509 utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
510 utf_out[2] = 0x80 | (ucs_char & 0x3f);
511 buffer_add(buf, utf_out);
513 /* ----------------------------------------------------------------------- */
514 /* ----------------------------------------------------------------------- */
521 buffer_add_char(buf, c);
527 buffer_add_char(buf, c);
534 jsonObjectSetString(obj, buf->buf);
540 void json_eat_ws(char* string, unsigned long* index, int eat_all) {
541 if( ! string || ! index ) return;
542 if(*index >= current_strlen)
545 if( eat_all ) { /* removes newlines, etc */
546 while(string[*index] == ' ' ||
547 string[*index] == '\n' ||
548 string[*index] == '\t')
553 while(string[*index] == ' ') (*index)++;
557 /* index should be at the '*' character at the beginning of the comment.
558 * when done, index will point to the first character after the final /
560 int json_eat_comment(char* string, unsigned long* index, char** buffer, int parse_class) {
561 if( ! string || ! index || *index >= current_strlen ) return -1;
564 if(string[*index] != '*' && string[*index] != '/' )
565 return json_handle_error(string, index,
566 "json_eat_comment(): invalid character after /");
568 /* chop out any // style comments */
569 if(string[*index] == '/') {
571 char c = string[*index];
572 while(*index < current_strlen) {
583 int on_star = 0; /* true if we just saw a '*' character */
585 /* we're just past the '*' */
586 if(!parse_class) { /* we're not concerned with class hints */
587 while(*index < current_strlen) {
588 if(string[*index] == '/') {
595 if(string[*index] == '*') on_star = 1;
605 growing_buffer* buf = buffer_init(64);
615 /*--S hint--*/ /* <-- Hints look like this */
618 while(*index < current_strlen) {
619 char c = string[*index];
625 if(third_dash) fourth_dash = 1;
626 else if(in_hint) third_dash = 1;
627 else if(first_dash) second_dash = 1;
633 if(second_dash && !in_hint) {
635 json_eat_ws(string, index, 1);
636 (*index)--; /* this will get incremented at the bottom of the loop */
641 if(second_dash && in_hint) {
642 buffer_add_char(buf, c);
648 if(second_dash && !in_hint) {
650 json_eat_ws(string, index, 1);
651 (*index)--; /* this will get incremented at the bottom of the loop */
656 if(second_dash && in_hint) {
657 buffer_add_char(buf, c);
675 buffer_add_char(buf, c);
682 if( buf->n_used > 0 && buffer)
683 *buffer = buffer_data(buf);
689 int is_number(char c) {
706 int json_handle_error(char* string, unsigned long* index, char* err_msg) {
712 strncpy( buf, string + (*index - 30), 59 );
714 strncpy( buf, string, 59 );
717 "\nError parsing json string at charracter %c "
718 "(code %d) and index %ld\nMsg:\t%s\nNear:\t%s\n\n",
719 string[*index], string[*index], *index, err_msg, buf );
724 jsonObject* jsonParseFile( const char* filename ) {
725 return json_parse_file( filename );
728 jsonObject* json_parse_file(const char* filename) {
729 if(!filename) return NULL;
730 char* data = file_to_string(filename);
731 jsonObject* o = json_parse_string(data);