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 int status = _json_parse_string(string, &index, obj);
47 int _json_parse_string(char* string, unsigned long* index, object* obj) {
48 assert(string && index && *index < current_strlen);
50 int status = 0; /* return code from parsing routines */
51 char* classname = NULL; /* object class hint */
52 json_eat_ws(string, index, 1); /* remove leading whitespace */
54 char c = string[*index];
56 /* remove any leading comments */
60 (*index)++; /* move to second comment char */
61 status = json_eat_comment(string, index, &classname, 1);
62 if(status) return status;
64 json_eat_ws(string, index, 1);
71 json_eat_ws(string, index, 1); /* remove leading whitespace */
73 if(*index >= current_strlen)
81 status = json_parse_json_string(string, index, obj);
87 status = json_parse_json_array(string, index, obj);
93 status = json_parse_json_object(string, index, obj);
99 status = json_parse_json_null(string, index, obj);
108 status = json_parse_json_bool(string, index, obj);
112 if(is_number(c) || c == '.' || c == '-') { /* are we a number? */
113 status = json_parse_json_number(string, index, obj);
114 if(status) return status;
119 /* we should never get here */
120 return json_handle_error(string, index, "_json_parse_string() final switch clause");
123 if(status) return status;
125 json_eat_ws(string, index, 1);
127 if( *index < current_strlen ) {
128 /* remove any trailing comments */
132 status = json_eat_comment(string, index, NULL, 0);
133 if(status) return status;
138 obj->set_class(obj, classname);
146 int json_parse_json_null(char* string, unsigned long* index, object* obj) {
148 if(*index >= (current_strlen - 3)) {
149 return json_handle_error(string, index,
150 "_parse_json_string(): invalid null" );
153 if(!strncasecmp(string + (*index), "null", 4)) {
158 return json_handle_error(string, index,
159 "_parse_json_string(): invalid null" );
163 /* should be at the first character of the bool at this point */
164 int json_parse_json_bool(char* string, unsigned long* index, object* obj) {
165 assert(string && obj && *index < current_strlen);
167 char* ret = "json_parse_json_bool(): truncated bool";
169 if( *index >= (current_strlen - 5))
170 return json_handle_error(string, index, ret);
172 if(!strncasecmp( string + (*index), "false", 5)) {
177 if( *index >= (current_strlen - 4))
178 return json_handle_error(string, index, ret);
180 if(!strncasecmp( string + (*index), "true", 4)) {
185 return json_handle_error(string, index, ret);
189 /* expecting the first character of the number */
190 int json_parse_json_number(char* string, unsigned long* index, object* obj) {
191 assert(string && obj && *index < current_strlen);
193 growing_buffer* buf = buffer_init(64);
194 char c = string[*index];
199 /* negative number? */
200 if(c == '-') { buffer_add(buf, "-"); (*index)++; }
202 while(*index < current_strlen) {
205 buffer_add_char(buf, c);
207 else if( c == '.' ) {
209 return json_handle_error(string, index,
210 "json_parse_json_number(): malformed json number");
213 buffer_add_char(buf, c);
224 obj->double_value = strtod(buf->buf, NULL);
230 obj->num_value = atol(buf->buf);
236 /* index should point to the character directly following the '['. when done
237 * index will point to the character directly following the ']' character
239 int json_parse_json_array(char* string, unsigned long* index, object* obj) {
240 assert(string && obj && index && *index < current_strlen);
243 int in_parse = 0; /* true if this array already contains one item */
245 while(*index < current_strlen) {
247 json_eat_ws(string, index, 1);
249 if(string[*index] == ']') {
255 json_eat_ws(string, index, 1);
256 if(string[*index] != ',') {
257 return json_handle_error(string, index,
258 "json_parse_json_array(): array not followed by a ','");
261 json_eat_ws(string, index, 1);
264 object* item = new_object(NULL);
265 status = _json_parse_string(string, index, item);
267 if(status) return status;
268 obj->push(obj, item);
276 /* index should point to the character directly following the '{'. when done
277 * index will point to the character directly following the '}'
279 int json_parse_json_object(char* string, unsigned long* index, object* obj) {
280 assert(string && obj && index && *index < current_strlen);
284 int in_parse = 0; /* true if we've already added one item to this object */
286 while(*index < current_strlen) {
288 json_eat_ws(string, index, 1);
290 if(string[*index] == '}') {
296 if(string[*index] != ',') {
297 return json_handle_error(string, index,
298 "json_parse_json_object(): object missing ',' betweenn elements" );
301 json_eat_ws(string, index, 1);
304 /* first we grab the hash key */
305 object* key_obj = new_object(NULL);
306 status = _json_parse_string(string, index, key_obj);
307 if(status) return status;
309 if(!key_obj->is_string) {
310 return json_handle_error(string, index,
311 "_json_parse_json_object(): hash key not a string");
314 char* key = key_obj->string_data;
316 json_eat_ws(string, index, 1);
318 if(string[*index] != ':') {
319 return json_handle_error(string, index,
320 "json_parse_json_object(): hash key not followed by ':' character");
325 /* now grab the value object */
326 json_eat_ws(string, index, 1);
327 object* value_obj = new_object(NULL);
328 status = _json_parse_string(string, index, value_obj);
329 if(status) return status;
331 /* put the data into the object and continue */
332 obj->add_key(obj, key, value_obj);
333 free_object(key_obj);
341 /* when done, index will point to the character after the closing quote */
342 int json_parse_json_string(char* string, unsigned long* index, object* obj) {
343 assert(string && index && *index < current_strlen);
347 growing_buffer* buf = buffer_init(64);
349 while(*index < current_strlen) {
351 char c = string[*index];
357 buffer_add(buf, "\\");
365 buffer_add(buf, "\"");
373 buffer_add(buf,"\t");
376 buffer_add_char(buf, c);
381 buffer_add(buf,"\b");
384 buffer_add_char(buf, c);
389 buffer_add(buf,"\f");
392 buffer_add_char(buf, c);
397 buffer_add(buf,"\r");
400 buffer_add_char(buf, c);
405 buffer_add(buf,"\n");
408 buffer_add_char(buf, c);
415 if(*index >= (current_strlen - 4)) {
416 return json_handle_error(string, index,
417 "json_parse_json_string(): truncated escaped unicode"); }
421 memcpy(buff, string + (*index), 4);
424 /* ----------------------------------------------------------------------- */
425 /* ----------------------------------------------------------------------- */
426 /* The following chunk was borrowed with permission from
427 json-c http://oss.metaparadigm.com/json-c/ */
428 unsigned char utf_out[3];
431 #define hexdigit(x) ( ((x) <= '9') ? (x) - '0' : ((x) & 7) + 9)
433 unsigned int ucs_char =
434 (hexdigit(string[*index] ) << 12) +
435 (hexdigit(string[*index + 1]) << 8) +
436 (hexdigit(string[*index + 2]) << 4) +
437 hexdigit(string[*index + 3]);
439 if (ucs_char < 0x80) {
440 utf_out[0] = ucs_char;
441 buffer_add(buf, utf_out);
443 } else if (ucs_char < 0x800) {
444 utf_out[0] = 0xc0 | (ucs_char >> 6);
445 utf_out[1] = 0x80 | (ucs_char & 0x3f);
446 buffer_add(buf, utf_out);
449 utf_out[0] = 0xe0 | (ucs_char >> 12);
450 utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
451 utf_out[2] = 0x80 | (ucs_char & 0x3f);
452 buffer_add(buf, utf_out);
454 /* ----------------------------------------------------------------------- */
455 /* ----------------------------------------------------------------------- */
462 buffer_add_char(buf, c);
468 buffer_add_char(buf, c);
475 obj->set_string(obj, buf->buf);
481 void json_eat_ws(char* string, unsigned long* index, int eat_all) {
482 assert(string && index);
483 if(*index >= current_strlen)
486 if( eat_all ) { /* removes newlines, etc */
487 while(string[*index] == ' ' ||
488 string[*index] == '\n' ||
489 string[*index] == '\t')
494 while(string[*index] == ' ') (*index)++;
498 /* index should be at the '*' character at the beginning of the comment.
499 * when done, index will point to the first character after the final /
501 int json_eat_comment(char* string, unsigned long* index, char** buffer, int parse_class) {
502 assert(string && index && *index < current_strlen);
504 if(string[*index] != '*' && string[*index] != '/' )
505 return json_handle_error(string, index,
506 "json_eat_comment(): invalid character after /");
508 /* chop out any // style comments */
509 if(string[*index] == '/') {
511 char c = string[*index];
512 while(*index < current_strlen) {
523 int on_star = 0; /* true if we just saw a '*' character */
525 /* we're just past the '*' */
526 if(!parse_class) { /* we're not concerned with class hints */
527 while(*index < current_strlen) {
528 if(string[*index] == '/') {
535 if(string[*index] == '*') on_star = 1;
545 growing_buffer* buf = buffer_init(64);
555 /*--S hint--*/ /* <-- Hints look like this */
558 while(*index < current_strlen) {
559 char c = string[*index];
565 if(third_dash) fourth_dash = 1;
566 else if(in_hint) third_dash = 1;
567 else if(first_dash) second_dash = 1;
573 if(second_dash && !in_hint) {
575 json_eat_ws(string, index, 1);
576 (*index)--; /* this will get incremented at the bottom of the loop */
583 if(second_dash && !in_hint) {
585 json_eat_ws(string, index, 1);
586 (*index)--; /* this will get incremented at the bottom of the loop */
605 buffer_add_char(buf, c);
612 if( buf->n_used > 0 && buffer)
613 *buffer = buffer_data(buf);
619 int is_number(char c) {
636 int json_handle_error(char* string, unsigned long* index, char* err_msg) {
642 strncpy( buf, string + (*index - 30), 59 );
644 strncpy( buf, string, 59 );
647 "\nError parsing json string at charracter %c "
648 "(code %d) and index %ld\nMsg:\t%s\nNear:\t%s\n\n",
649 string[*index], string[*index], *index, err_msg, buf );