]> git.evergreen-ils.org Git - working/Evergreen.git/blob - OpenSRF/src/libjson/json_tokener.c
sort groups by name for consistancy
[working/Evergreen.git] / OpenSRF / src / libjson / json_tokener.c
1 /*
2  * $Id$
3  *
4  * Copyright Metaparadigm Pte. Ltd. 2004.
5  * Michael Clark <michael@metaparadigm.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public (LGPL)
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details: http://www.gnu.org/
16  *
17  */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <ctype.h>
22 #include <string.h>
23
24 #include "json_tokener.h"
25 #include "bits.h"
26 #include "debug.h"
27 #include "printbuf.h"
28 #include "linkhash.h"
29 #include "arraylist.h"
30 #include "json_object.h"
31 #include "ossupport.h"
32 #include "json_object_private.h"
33
34
35
36 static struct json_object* json_tokener_do_parse(struct json_tokener *this);
37
38 struct json_object* json_tokener_parse(char * s)
39 {
40   struct json_tokener tok;
41   struct json_object* obj;
42
43   tok.source = s;
44   tok.pos = 0;
45   tok.pb = printbuf_new();
46   obj = json_tokener_do_parse(&tok);
47   printbuf_free(tok.pb);
48   return obj;
49 }
50
51 static struct json_object* json_tokener_do_parse(struct json_tokener *this)
52 {
53   enum json_tokener_state state, saved_state;
54   enum json_tokener_error err = json_tokener_success;
55   struct json_object *current = NULL, *obj;
56   char *obj_field_name = NULL;
57   char quote_char;
58   int deemed_double, start_offset;
59
60   state = json_tokener_state_eatws;
61   saved_state = json_tokener_state_start;
62
63   char c;
64   do {
65     c = this->source[this->pos];
66     switch(state) {
67
68     case json_tokener_state_eatws:
69       if(isspace(c)) {
70         this->pos++;
71       } else if(c == '/') {
72         state = json_tokener_state_comment_start;
73         start_offset = this->pos++;
74       } else {
75         state = saved_state;
76       }
77       break;
78
79     case json_tokener_state_start:
80       switch(c) {
81       case '{':
82         state = json_tokener_state_eatws;
83         saved_state = json_tokener_state_object;
84         current = json_object_new_object();
85         this->pos++;
86         break;
87       case '[':
88         state = json_tokener_state_eatws;
89         saved_state = json_tokener_state_array;
90         current = json_object_new_array();
91         this->pos++;
92         break;
93       case 'N':
94       case 'n':
95         state = json_tokener_state_null;
96         start_offset = this->pos++;
97         break;
98       case '"':
99       case '\'':
100         quote_char = c;
101         printbuf_reset(this->pb);
102         state = json_tokener_state_string;
103         start_offset = ++this->pos;
104         break;
105       case 'T':
106       case 't':
107       case 'F':
108       case 'f':
109         state = json_tokener_state_boolean;
110         start_offset = this->pos++;
111         break;
112       case '0' ... '9':
113       case '-':
114         deemed_double = 0;
115         state = json_tokener_state_number;
116         start_offset = this->pos++;
117         break;
118       default:
119         err = json_tokener_error_parse_unexpected;
120         goto out;
121       }
122       break;
123
124     case json_tokener_state_finish:
125       goto out;
126
127     case json_tokener_state_null:
128       if(strncasecmp("null", this->source + start_offset,
129                      this->pos - start_offset))
130         return error_ptr(-json_tokener_error_parse_null);
131       if(this->pos - start_offset == 4) {
132         current = NULL;
133         saved_state = json_tokener_state_finish;
134         state = json_tokener_state_eatws;
135       } else {
136         this->pos++;
137       }
138       break;
139
140     case json_tokener_state_comment_start:
141       if(c == '*') {
142         state = json_tokener_state_comment;
143       } else if(c == '/') {
144         state = json_tokener_state_comment_eol;
145       } else {
146         err = json_tokener_error_parse_comment;
147         goto out;
148       }
149       this->pos++;
150       break;
151
152     case json_tokener_state_comment:
153       if(c == '*') state = json_tokener_state_comment_end;
154       this->pos++;
155       break;
156
157     case json_tokener_state_comment_eol:
158       if(c == '\n') {
159         if(mc_get_debug()) {
160           char *tmp = strndup(this->source + start_offset,
161                               this->pos - start_offset);
162           mc_debug("json_tokener_comment: %s\n", tmp);
163           free(tmp);
164         }
165         state = json_tokener_state_eatws;
166       }
167       this->pos++;
168       break;
169
170     case json_tokener_state_comment_end:
171       if(c == '/') {
172         if(mc_get_debug()) {
173           char *tmp = strndup(this->source + start_offset,
174                               this->pos - start_offset + 1);
175           mc_debug("json_tokener_comment: %s\n", tmp);
176           free(tmp);
177         }
178         state = json_tokener_state_eatws;
179       } else {
180         state = json_tokener_state_comment;
181       }
182       this->pos++;
183       break;
184
185     case json_tokener_state_string:
186       if(c == quote_char) {
187         printbuf_memappend(this->pb, this->source + start_offset,
188                            this->pos - start_offset);
189         current = json_object_new_string(this->pb->buf);
190         saved_state = json_tokener_state_finish;
191         state = json_tokener_state_eatws;
192       } else if(c == '\\') {
193         saved_state = json_tokener_state_string;
194         state = json_tokener_state_string_escape;
195       }
196       this->pos++;
197       break;
198
199     case json_tokener_state_string_escape:
200       switch(c) {
201       case '"':
202       case '\\':
203       case '/':
204         printbuf_memappend(this->pb, this->source + start_offset,
205                            this->pos - start_offset - 1);
206         start_offset = this->pos++;
207         state = saved_state;
208         break;
209       case 'b':
210       case 'n':
211       case 'r':
212       case 't':
213         printbuf_memappend(this->pb, this->source + start_offset,
214                            this->pos - start_offset - 1);
215         if(c == 'b') printbuf_memappend(this->pb, "\b", 1);
216         else if(c == 'n') printbuf_memappend(this->pb, "\n", 1);
217         else if(c == 'r') printbuf_memappend(this->pb, "\r", 1);
218         else if(c == 't') printbuf_memappend(this->pb, "\t", 1);
219         start_offset = ++this->pos;
220         state = saved_state;
221         break;
222       case 'u':
223         printbuf_memappend(this->pb, this->source + start_offset,
224                            this->pos - start_offset - 1);
225         start_offset = ++this->pos;
226         state = json_tokener_state_escape_unicode;
227         break;
228       default:
229         err = json_tokener_error_parse_string;
230         goto out;
231       }
232       break;
233
234         case json_tokener_state_escape_unicode:
235
236                 if(strchr(json_hex_chars, c)) {
237                         this->pos++;
238
239                         if(this->pos - start_offset == 4) {
240                                 unsigned char utf_out[3];
241                                 unsigned int ucs_char =
242                                         (hexdigit(*(this->source + start_offset)) << 12) +
243                                         (hexdigit(*(this->source + start_offset + 1)) << 8) +
244                                         (hexdigit(*(this->source + start_offset + 2)) << 4) +
245                                         hexdigit(*(this->source + start_offset + 3));
246
247                                 if (ucs_char < 0x80) {
248                                         utf_out[0] = ucs_char;
249                                         printbuf_memappend(this->pb, utf_out, 1);
250                                 } else if (ucs_char < 0x800) {
251                                         utf_out[0] = 0xc0 | (ucs_char >> 6);
252                                         utf_out[1] = 0x80 | (ucs_char & 0x3f);
253                                         printbuf_memappend(this->pb, utf_out, 2);
254                                 } else {
255                                         utf_out[0] = 0xe0 | (ucs_char >> 12);
256                                         utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
257                                         utf_out[2] = 0x80 | (ucs_char & 0x3f);
258                                         printbuf_memappend(this->pb, utf_out, 3);
259                                 }
260                                 start_offset = this->pos;
261                                 state = saved_state;
262                                 }
263                         } else {
264                                 err = json_tokener_error_parse_string;
265                                 goto out;
266         }
267         break;
268
269     case json_tokener_state_boolean:
270       if(strncasecmp("true", this->source + start_offset,
271                  this->pos - start_offset) == 0) {
272         if(this->pos - start_offset == 4) {
273           current = json_object_new_boolean(1);
274           saved_state = json_tokener_state_finish;
275           state = json_tokener_state_eatws;
276         } else {
277           this->pos++;
278         }
279       } else if(strncasecmp("false", this->source + start_offset,
280                         this->pos - start_offset) == 0) {
281         if(this->pos - start_offset == 5) {
282           current = json_object_new_boolean(0);
283           saved_state = json_tokener_state_finish;
284           state = json_tokener_state_eatws;
285         } else {
286           this->pos++;
287         }
288       } else {
289         err = json_tokener_error_parse_boolean;
290         goto out;
291       }
292       break;
293
294     case json_tokener_state_number:
295       if(!c || !strchr(json_number_chars, c)) {
296         int numi;
297         double numd;
298         char *tmp = strndup(this->source + start_offset,
299                             this->pos - start_offset);
300         if(!deemed_double && sscanf(tmp, "%d", &numi) == 1) {
301           current = json_object_new_int(numi);
302         } else if(deemed_double && sscanf(tmp, "%lf", &numd) == 1) {
303           current = json_object_new_double(numd);
304         } else {
305           free(tmp);
306           err = json_tokener_error_parse_number;
307           goto out;
308         }
309         free(tmp);
310         saved_state = json_tokener_state_finish;
311         state = json_tokener_state_eatws;
312       } else {
313         if(c == '.' || c == 'e') deemed_double = 1;
314         this->pos++;
315       }
316       break;
317
318     case json_tokener_state_array:
319       if(c == ']') {
320         this->pos++;
321         saved_state = json_tokener_state_finish;
322         state = json_tokener_state_eatws;
323       } else {
324         obj = json_tokener_do_parse(this);
325         if(is_error(obj)) {
326           err = (enum json_tokener_error)obj;
327           goto out;
328         }
329         json_object_array_add(current, obj);
330         saved_state = json_tokener_state_array_sep;
331         state = json_tokener_state_eatws;
332       }
333       break;
334
335     case json_tokener_state_array_sep:
336       if(c == ']') {
337         this->pos++;
338         saved_state = json_tokener_state_finish;
339         state = json_tokener_state_eatws;
340       } else if(c == ',') {
341         this->pos++;
342         saved_state = json_tokener_state_array;
343         state = json_tokener_state_eatws;
344       } else {
345         json_object_put(current);
346         return error_ptr(-json_tokener_error_parse_array);
347       }
348       break;
349
350     case json_tokener_state_object:
351       state = json_tokener_state_object_field_start;
352       start_offset = this->pos;
353       break;
354
355     case json_tokener_state_object_field_start:
356       if(c == '}') {
357         this->pos++;
358         saved_state = json_tokener_state_finish;
359         state = json_tokener_state_eatws;
360       } else if (c == '"' || c == '\'') {
361         quote_char = c;
362         printbuf_reset(this->pb);
363         state = json_tokener_state_object_field;
364         start_offset = ++this->pos;
365       }
366       break;
367
368     case json_tokener_state_object_field:
369       if(c == quote_char) {
370         printbuf_memappend(this->pb, this->source + start_offset,
371                            this->pos - start_offset);
372         obj_field_name = strdup(this->pb->buf);
373         saved_state = json_tokener_state_object_field_end;
374         state = json_tokener_state_eatws;
375       } else if(c == '\\') {
376         saved_state = json_tokener_state_object_field;
377         state = json_tokener_state_string_escape;
378       }
379       this->pos++;
380       break;
381
382     case json_tokener_state_object_field_end:
383       if(c == ':') {
384         this->pos++;
385         saved_state = json_tokener_state_object_value;
386         state = json_tokener_state_eatws;
387       } else {
388         return error_ptr(-json_tokener_error_parse_object);
389       }
390       break;
391
392     case json_tokener_state_object_value:
393       obj = json_tokener_do_parse(this);
394       if(is_error(obj)) {
395         err = (enum json_tokener_error)obj;
396         goto out;
397       }
398       json_object_object_add(current, obj_field_name, obj);
399       free(obj_field_name);
400       obj_field_name = NULL;
401       saved_state = json_tokener_state_object_sep;
402       state = json_tokener_state_eatws;
403       break;
404
405     case json_tokener_state_object_sep:
406       if(c == '}') {
407         this->pos++;
408         saved_state = json_tokener_state_finish;
409         state = json_tokener_state_eatws;
410       } else if(c == ',') {
411         this->pos++;
412         saved_state = json_tokener_state_object;
413         state = json_tokener_state_eatws;
414       } else {
415         err = json_tokener_error_parse_object;
416         goto out;
417       }
418       break;
419
420     }
421   } while(c);
422
423   if(state != json_tokener_state_finish &&
424      saved_state != json_tokener_state_finish)
425     err = json_tokener_error_parse_eof;
426
427  out:
428   free(obj_field_name);
429   if(err == json_tokener_success) return current;
430   mc_debug("json_tokener_do_parse: error=%d state=%d char=%c\n",
431            err, state, c);
432   json_object_put(current);
433   return error_ptr(-err);
434 }