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 */
24 object* json_parse_string(char* string) {
26 if(string == NULL) return NULL;
28 current_strlen = strlen(string);
30 if(current_strlen == 0)
33 object* obj = new_object(NULL);
34 unsigned long index = 0;
36 json_eat_ws(string, &index, 1); /* remove leading whitespace */
37 if(index == current_strlen) return NULL;
39 int status = _json_parse_string(string, &index, obj);
40 if(!status) return obj;
49 int _json_parse_string(char* string, unsigned long* index, object* obj) {
50 assert(string && index && *index < current_strlen);
52 int status = 0; /* return code from parsing routines */
53 char* classname = NULL; /* object class hint */
54 json_eat_ws(string, index, 1); /* remove leading whitespace */
56 char c = string[*index];
58 /* remove any leading comments */
62 (*index)++; /* move to second comment char */
63 status = json_eat_comment(string, index, &classname, 1);
64 if(status) return status;
66 json_eat_ws(string, index, 1);
73 json_eat_ws(string, index, 1); /* remove leading whitespace */
75 if(*index >= current_strlen)
83 status = json_parse_json_string(string, index, obj);
89 status = json_parse_json_array(string, index, obj);
95 status = json_parse_json_object(string, index, obj);
101 status = json_parse_json_null(string, index, obj);
110 status = json_parse_json_bool(string, index, obj);
114 if(is_number(c) || c == '.' || c == '-') { /* are we a number? */
115 status = json_parse_json_number(string, index, obj);
116 if(status) return status;
121 /* we should never get here */
122 return json_handle_error(string, index, "_json_parse_string() final switch clause");
125 if(status) return status;
127 json_eat_ws(string, index, 1);
129 if( *index < current_strlen ) {
130 /* remove any trailing comments */
134 status = json_eat_comment(string, index, NULL, 0);
135 if(status) return status;
140 obj->set_class(obj, classname);
148 int json_parse_json_null(char* string, unsigned long* index, object* obj) {
150 if(*index >= (current_strlen - 3)) {
151 return json_handle_error(string, index,
152 "_parse_json_string(): invalid null" );
155 if(!strncasecmp(string + (*index), "null", 4)) {
160 return json_handle_error(string, index,
161 "_parse_json_string(): invalid null" );
165 /* should be at the first character of the bool at this point */
166 int json_parse_json_bool(char* string, unsigned long* index, object* obj) {
167 assert(string && obj && *index < current_strlen);
169 char* ret = "json_parse_json_bool(): truncated bool";
171 if( *index >= (current_strlen - 5))
172 return json_handle_error(string, index, ret);
174 if(!strncasecmp( string + (*index), "false", 5)) {
182 if( *index >= (current_strlen - 4))
183 return json_handle_error(string, index, ret);
185 if(!strncasecmp( string + (*index), "true", 4)) {
193 return json_handle_error(string, index, ret);
197 /* expecting the first character of the number */
198 int json_parse_json_number(char* string, unsigned long* index, object* obj) {
199 assert(string && obj && *index < current_strlen);
201 growing_buffer* buf = buffer_init(64);
202 char c = string[*index];
207 /* negative number? */
208 if(c == '-') { buffer_add(buf, "-"); (*index)++; }
210 while(*index < current_strlen) {
213 buffer_add_char(buf, c);
215 else if( c == '.' ) {
217 return json_handle_error(string, index,
218 "json_parse_json_number(): malformed json number");
221 buffer_add_char(buf, c);
233 obj->double_value = strtod(buf->buf, NULL);
240 obj->num_value = atol(buf->buf);
246 /* index should point to the character directly following the '['. when done
247 * index will point to the character directly following the ']' character
249 int json_parse_json_array(char* string, unsigned long* index, object* obj) {
250 assert(string && obj && index && *index < current_strlen);
253 int in_parse = 0; /* true if this array already contains one item */
259 while(*index < current_strlen) {
261 json_eat_ws(string, index, 1);
263 if(string[*index] == ']') {
270 json_eat_ws(string, index, 1);
271 if(string[*index] != ',') {
272 return json_handle_error(string, index,
273 "json_parse_json_array(): array not followed by a ','");
276 json_eat_ws(string, index, 1);
279 object* item = new_object(NULL);
281 #ifndef STRICT_JSON_READ
282 if(*index < current_strlen) {
283 if(string[*index] == ',' || string[*index] == ']') {
289 status = _json_parse_string(string, index, item);
292 status = _json_parse_string(string, index, item);
295 if(status) return status;
296 obj->push(obj, item);
302 return json_handle_error(string, index,
303 "json_parse_json_array(): array not closed");
309 /* index should point to the character directly following the '{'. when done
310 * index will point to the character directly following the '}'
312 int json_parse_json_object(char* string, unsigned long* index, object* obj) {
313 assert(string && obj && index && *index < current_strlen);
318 int in_parse = 0; /* true if we've already added one item to this object */
322 while(*index < current_strlen) {
324 json_eat_ws(string, index, 1);
326 if(string[*index] == '}') {
333 if(string[*index] != ',') {
334 return json_handle_error(string, index,
335 "json_parse_json_object(): object missing ',' between elements" );
338 json_eat_ws(string, index, 1);
341 /* first we grab the hash key */
342 object* key_obj = new_object(NULL);
343 status = _json_parse_string(string, index, key_obj);
344 if(status) return status;
346 if(!key_obj->is_string) {
347 return json_handle_error(string, index,
348 "_json_parse_json_object(): hash key not a string");
351 char* key = key_obj->string_data;
353 json_eat_ws(string, index, 1);
355 if(string[*index] != ':') {
356 return json_handle_error(string, index,
357 "json_parse_json_object(): hash key not followed by ':' character");
362 /* now grab the value object */
363 json_eat_ws(string, index, 1);
364 object* value_obj = new_object(NULL);
366 #ifndef STRICT_JSON_READ
367 if(*index < current_strlen) {
368 if(string[*index] == ',' || string[*index] == '}') {
374 status = _json_parse_string(string, index, value_obj);
377 status = _json_parse_string(string, index, value_obj);
380 if(status) return status;
382 /* put the data into the object and continue */
383 obj->add_key(obj, key, value_obj);
384 free_object(key_obj);
390 return json_handle_error(string, index,
391 "json_parse_json_object(): object not closed");
398 /* when done, index will point to the character after the closing quote */
399 int json_parse_json_string(char* string, unsigned long* index, object* obj) {
400 assert(string && index && *index < current_strlen);
404 growing_buffer* buf = buffer_init(64);
406 while(*index < current_strlen) {
408 char c = string[*index];
414 buffer_add(buf, "\\");
422 buffer_add(buf, "\"");
430 buffer_add(buf,"\t");
433 buffer_add_char(buf, c);
438 buffer_add(buf,"\b");
441 buffer_add_char(buf, c);
446 buffer_add(buf,"\f");
449 buffer_add_char(buf, c);
454 buffer_add(buf,"\r");
457 buffer_add_char(buf, c);
462 buffer_add(buf,"\n");
465 buffer_add_char(buf, c);
472 if(*index >= (current_strlen - 4)) {
473 return json_handle_error(string, index,
474 "json_parse_json_string(): truncated escaped unicode"); }
478 memcpy(buff, string + (*index), 4);
481 /* ----------------------------------------------------------------------- */
482 /* ----------------------------------------------------------------------- */
483 /* The following chunk was borrowed with permission from
484 json-c http://oss.metaparadigm.com/json-c/ */
485 unsigned char utf_out[3];
488 #define hexdigit(x) ( ((x) <= '9') ? (x) - '0' : ((x) & 7) + 9)
490 unsigned int ucs_char =
491 (hexdigit(string[*index] ) << 12) +
492 (hexdigit(string[*index + 1]) << 8) +
493 (hexdigit(string[*index + 2]) << 4) +
494 hexdigit(string[*index + 3]);
496 if (ucs_char < 0x80) {
497 utf_out[0] = ucs_char;
498 buffer_add(buf, utf_out);
500 } else if (ucs_char < 0x800) {
501 utf_out[0] = 0xc0 | (ucs_char >> 6);
502 utf_out[1] = 0x80 | (ucs_char & 0x3f);
503 buffer_add(buf, utf_out);
506 utf_out[0] = 0xe0 | (ucs_char >> 12);
507 utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
508 utf_out[2] = 0x80 | (ucs_char & 0x3f);
509 buffer_add(buf, utf_out);
511 /* ----------------------------------------------------------------------- */
512 /* ----------------------------------------------------------------------- */
519 buffer_add_char(buf, c);
525 buffer_add_char(buf, c);
532 obj->set_string(obj, buf->buf);
538 void json_eat_ws(char* string, unsigned long* index, int eat_all) {
539 assert(string && index);
540 if(*index >= current_strlen)
543 if( eat_all ) { /* removes newlines, etc */
544 while(string[*index] == ' ' ||
545 string[*index] == '\n' ||
546 string[*index] == '\t')
551 while(string[*index] == ' ') (*index)++;
555 /* index should be at the '*' character at the beginning of the comment.
556 * when done, index will point to the first character after the final /
558 int json_eat_comment(char* string, unsigned long* index, char** buffer, int parse_class) {
559 assert(string && index && *index < current_strlen);
561 if(string[*index] != '*' && string[*index] != '/' )
562 return json_handle_error(string, index,
563 "json_eat_comment(): invalid character after /");
565 /* chop out any // style comments */
566 if(string[*index] == '/') {
568 char c = string[*index];
569 while(*index < current_strlen) {
580 int on_star = 0; /* true if we just saw a '*' character */
582 /* we're just past the '*' */
583 if(!parse_class) { /* we're not concerned with class hints */
584 while(*index < current_strlen) {
585 if(string[*index] == '/') {
592 if(string[*index] == '*') on_star = 1;
602 growing_buffer* buf = buffer_init(64);
612 /*--S hint--*/ /* <-- Hints look like this */
615 while(*index < current_strlen) {
616 char c = string[*index];
622 if(third_dash) fourth_dash = 1;
623 else if(in_hint) third_dash = 1;
624 else if(first_dash) second_dash = 1;
630 if(second_dash && !in_hint) {
632 json_eat_ws(string, index, 1);
633 (*index)--; /* this will get incremented at the bottom of the loop */
638 if(second_dash && in_hint) {
639 buffer_add_char(buf, c);
645 if(second_dash && !in_hint) {
647 json_eat_ws(string, index, 1);
648 (*index)--; /* this will get incremented at the bottom of the loop */
653 if(second_dash && in_hint) {
654 buffer_add_char(buf, c);
672 buffer_add_char(buf, c);
679 if( buf->n_used > 0 && buffer)
680 *buffer = buffer_data(buf);
686 int is_number(char c) {
703 int json_handle_error(char* string, unsigned long* index, char* err_msg) {
709 strncpy( buf, string + (*index - 30), 59 );
711 strncpy( buf, string, 59 );
714 "\nError parsing json string at charracter %c "
715 "(code %d) and index %ld\nMsg:\t%s\nNear:\t%s\n\n",
716 string[*index], string[*index], *index, err_msg, buf );