/src/netcdf-c/libdispatch/ncjson.c
Line | Count | Source |
1 | | /* Copyright 2018, UCAR/Unidata. |
2 | | See the COPYRIGHT file for more information. |
3 | | */ |
4 | | |
5 | | /* |
6 | | TODO: make utf8 safe |
7 | | */ |
8 | | |
9 | | /* |
10 | | WARNING: |
11 | | If you modify this file, |
12 | | then you need to go to |
13 | | the include/ directory |
14 | | and do the command: |
15 | | make netcdf_json.h |
16 | | */ |
17 | | |
18 | | #ifdef HAVE_CONFIG_H |
19 | | #include "config.h" |
20 | | #endif /*HAVE_CONFIG_H*/ |
21 | | #include <stdlib.h> |
22 | | #include <stdio.h> |
23 | | #include <string.h> |
24 | | #include <assert.h> |
25 | | |
26 | | #include "ncjson.h" |
27 | | |
28 | | #undef NCJCATCH |
29 | | #undef NCJDEBUG |
30 | | #undef NCJTRACE |
31 | | |
32 | | #ifdef NCJCATCH |
33 | | /* Warning: do not evaluate err more than once */ |
34 | | #define NCJTHROW(err) ncjbreakpoint(err) |
35 | | static int ncjbreakpoint(int err) {return err;} |
36 | | #else /*!NCJCATCH*/ |
37 | 0 | #define NCJTHROW(err) (err) |
38 | | #endif /*NCJCATCH*/ |
39 | | |
40 | | /**************************************************/ |
41 | | |
42 | 0 | #define NCJ_EOF -2 |
43 | | |
44 | 0 | #define NCJ_LBRACKET '[' |
45 | 0 | #define NCJ_RBRACKET ']' |
46 | 0 | #define NCJ_LBRACE '{' |
47 | 0 | #define NCJ_RBRACE '}' |
48 | 0 | #define NCJ_COLON ':' |
49 | 0 | #define NCJ_COMMA ',' |
50 | 0 | #define NCJ_QUOTE '"' |
51 | 0 | #define NCJ_ESCAPE '\\' |
52 | 0 | #define NCJ_TAG_TRUE "true" |
53 | 0 | #define NCJ_TAG_FALSE "false" |
54 | 0 | #define NCJ_TAG_NULL "null" |
55 | | |
56 | | /* JSON_WORD Subsumes Number also */ |
57 | 0 | #define JSON_WORD "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$+-." |
58 | | |
59 | | #define NCJ_DEFAULTALLOC 16 |
60 | | |
61 | | /**************************************************/ |
62 | | typedef struct NCJparser { |
63 | | char* text; |
64 | | char* pos; |
65 | | size_t yylen; /* |yytext| */ |
66 | | char* yytext; /* string or word */ |
67 | | long long num; |
68 | | int tf; |
69 | | int status; /* NCJ_ERR|NCJ_OK */ |
70 | | unsigned flags; |
71 | | # define NCJ_TRACE 1 |
72 | | } NCJparser; |
73 | | |
74 | | /* This is used only by the unparser */ |
75 | | typedef struct NCJbuf { |
76 | | size_t len; /* |text|; does not include nul terminator */ |
77 | | char* text; /* NULL || nul terminated */ |
78 | | } NCJbuf; |
79 | | |
80 | | /**************************************************/ |
81 | | |
82 | | #if defined(_WIN32) && !defined(__MINGW32__) |
83 | | #define strdup _strdup |
84 | | #define strcasecmp _stricmp |
85 | | #else /*!WIN32 || __MINGW32*/ |
86 | | #include <strings.h> |
87 | | #endif /*defined(_WIN32) && !defined(__MINGW32__)*/ |
88 | | |
89 | | #ifndef nullfree |
90 | | #define nullfree(x) {if(x)free(x);} |
91 | | #endif /*nullfree*/ |
92 | | #ifndef nulldup |
93 | | #define nulldup(x) ((x)?strdup(x):(x)) |
94 | | #endif /*nulldup*/ |
95 | | |
96 | | #if defined NCJDEBUG || defined NCJTRACE |
97 | | static char* tokenname(int token); |
98 | | #endif /*defined NCJDEBUG || defined NCJTRACE*/ |
99 | | |
100 | | /**************************************************/ |
101 | | /* Forward */ |
102 | | static int NCJparseR(NCJparser* parser, NCjson**); |
103 | | static int NCJparseArray(NCJparser* parser, struct NCjlist* array); |
104 | | static int NCJparseDict(NCJparser* parser, struct NCjlist* dict); |
105 | | static int testbool(const char* word); |
106 | | static int testint(const char* word); |
107 | | static int testdouble(const char* word); |
108 | | static int testnull(const char* word); |
109 | | static int NCJlex(NCJparser* parser); |
110 | | static int NCJyytext(NCJparser*, char* start, size_t pdlen); |
111 | | static void NCJreclaimArray(struct NCjlist*); |
112 | | static void NCJreclaimDict(struct NCjlist*); |
113 | | static int NCJunescape(NCJparser* parser); |
114 | | static char unescape1(char c); |
115 | | |
116 | | static int listappend(struct NCjlist* list, NCjson* element); |
117 | | static int listsetalloc(struct NCjlist* list, size_t sz); |
118 | | static int listlookup(const struct NCjlist* list, const char* key, size_t* indexp); |
119 | | |
120 | | static int NCJcloneArray(const NCjson* array, NCjson** clonep); |
121 | | static int NCJcloneDict(const NCjson* dict, NCjson** clonep); |
122 | | |
123 | | /* These are used only by the unparser */ |
124 | | static int NCJunparseR(const NCjson* json, NCJbuf* buf, unsigned flags); |
125 | | static int bytesappendquoted(NCJbuf* buf, const char* s); |
126 | | static int bytesappend(NCJbuf* buf, const char* s); |
127 | | static int bytesappendc(NCJbuf* bufp, char c); |
128 | | |
129 | | /* Hide everything for plugins */ |
130 | | #ifdef NETCDF_JSON_H |
131 | | #define OPTSTATIC static |
132 | | #else /*!NETCDF_JSON_H*/ |
133 | | #define OPTSTATIC |
134 | | #endif /*NETCDF_JSON_H*/ |
135 | | |
136 | | /* List legal nan and infinity names (lower case); keep in strcasecmp sorted order */ |
137 | | static const char* NANINF[] = {"-infinity","infinity","-infinityf","infinityf","nan","nanf"}; |
138 | | static const size_t NNANINF = 6; |
139 | | |
140 | | /**************************************************/ |
141 | | |
142 | | OPTSTATIC int |
143 | | NCJparse(const char* text, unsigned flags, NCjson** jsonp) |
144 | 0 | { |
145 | 0 | size_t textlen = strlen(text); |
146 | 0 | return NCJparsen(textlen,text,flags,jsonp); |
147 | 0 | } |
148 | | |
149 | | OPTSTATIC int |
150 | | NCJparsen(size_t len, const char* text, unsigned flags, NCjson** jsonp) |
151 | 0 | { |
152 | 0 | int stat = NCJ_OK; |
153 | 0 | NCJparser* parser = NULL; |
154 | 0 | NCjson* json = NULL; |
155 | |
|
156 | 0 | parser = calloc(1,sizeof(NCJparser)); |
157 | 0 | if(parser == NULL) |
158 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
159 | 0 | parser->flags = flags; |
160 | 0 | parser->text = (char*)malloc(len+1+1); |
161 | 0 | if(parser->text == NULL) |
162 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
163 | 0 | memcpy(parser->text,text,len); |
164 | | /* trim trailing whitespace */ |
165 | 0 | if(len > 0) { |
166 | 0 | char* p; |
167 | 0 | for(p=parser->text+(len-1);p >= parser->text;p--) { |
168 | 0 | if(*p > ' ') break; |
169 | 0 | } |
170 | 0 | len = (size_t)((p - parser->text) + 1); |
171 | 0 | } |
172 | 0 | if(len == 0) |
173 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
174 | 0 | parser->text[len] = '\0'; |
175 | 0 | parser->text[len+1] = '\0'; |
176 | 0 | parser->pos = &parser->text[0]; |
177 | 0 | parser->status = NCJ_OK; |
178 | | #ifdef NCJDEBUG |
179 | | fprintf(stderr,"json: |%s|\n",parser->text); |
180 | | #endif /*NCJDEBUG*/ |
181 | 0 | if((stat=NCJparseR(parser,&json))==NCJ_ERR) goto done; |
182 | | /* Must consume all of the input */ |
183 | 0 | if(parser->pos != (parser->text+len)) {stat = NCJ_ERR; goto done;} |
184 | 0 | *jsonp = json; |
185 | 0 | json = NULL; |
186 | |
|
187 | 0 | done: |
188 | 0 | if(parser != NULL) { |
189 | 0 | nullfree(parser->text); |
190 | 0 | nullfree(parser->yytext); |
191 | 0 | free(parser); |
192 | 0 | } |
193 | 0 | (void)NCJreclaim(json); |
194 | 0 | return NCJTHROW(stat); |
195 | 0 | } |
196 | | |
197 | | /* |
198 | | Simple recursive descent |
199 | | intertwined with dict and list parsers. |
200 | | |
201 | | Invariants: |
202 | | 1. The json argument is provided by caller and filled in by NCJparseR. |
203 | | 2. Each call pushed back last unconsumed token |
204 | | */ |
205 | | |
206 | | static int |
207 | | NCJparseR(NCJparser* parser, NCjson** jsonp) |
208 | 0 | { |
209 | 0 | int stat = NCJ_OK; |
210 | 0 | int token = NCJ_UNDEF; |
211 | 0 | NCjson* json = NULL; |
212 | |
|
213 | 0 | if(jsonp == NULL) |
214 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
215 | 0 | if((token = NCJlex(parser)) == NCJ_UNDEF) |
216 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
217 | 0 | switch (token) { |
218 | 0 | case NCJ_EOF: |
219 | 0 | break; |
220 | 0 | case NCJ_NULL: |
221 | 0 | if((stat = NCJnew(NCJ_NULL,&json))==NCJ_ERR) goto done; |
222 | 0 | break; |
223 | 0 | case NCJ_BOOLEAN: |
224 | 0 | if((stat = NCJnew(NCJ_BOOLEAN,&json))==NCJ_ERR) goto done; |
225 | 0 | json->string = strdup(parser->yytext); |
226 | 0 | break; |
227 | 0 | case NCJ_INT: |
228 | 0 | if((stat = NCJnew(NCJ_INT,&json))==NCJ_ERR) goto done; |
229 | 0 | json->string = strdup(parser->yytext); |
230 | 0 | break; |
231 | 0 | case NCJ_DOUBLE: |
232 | 0 | if((stat = NCJnew(NCJ_DOUBLE,&json))==NCJ_ERR) goto done; |
233 | 0 | json->string = strdup(parser->yytext); |
234 | 0 | break; |
235 | 0 | case NCJ_STRING: |
236 | 0 | if((stat = NCJnew(NCJ_STRING,&json))==NCJ_ERR) goto done; |
237 | 0 | json->string = strdup(parser->yytext); |
238 | 0 | break; |
239 | 0 | case NCJ_LBRACE: |
240 | 0 | if((stat = NCJnew(NCJ_DICT,&json))==NCJ_ERR) goto done; |
241 | 0 | if((stat = NCJparseDict(parser, &json->list))==NCJ_ERR) goto done; |
242 | 0 | break; |
243 | 0 | case NCJ_LBRACKET: |
244 | 0 | if((stat = NCJnew(NCJ_ARRAY,&json))==NCJ_ERR) goto done; |
245 | 0 | if((stat = NCJparseArray(parser, &json->list))==NCJ_ERR) goto done; |
246 | 0 | break; |
247 | 0 | case NCJ_RBRACE: /* We hit end of the dict we are parsing */ |
248 | 0 | parser->pos--; /* pushback so NCJparseArray will catch */ |
249 | 0 | json = NULL; |
250 | 0 | break; |
251 | 0 | case NCJ_RBRACKET: |
252 | 0 | parser->pos--; /* pushback so NCJparseDict will catch */ |
253 | 0 | json = NULL; |
254 | 0 | break; |
255 | 0 | default: |
256 | 0 | stat = NCJTHROW(NCJ_ERR); |
257 | 0 | break; |
258 | 0 | } |
259 | 0 | if(jsonp && json) {*jsonp = json; json = NULL;} |
260 | |
|
261 | 0 | done: |
262 | 0 | NCJreclaim(json); |
263 | 0 | return NCJTHROW(stat); |
264 | 0 | } |
265 | | |
266 | | static int |
267 | | NCJparseArray(NCJparser* parser, struct NCjlist* arrayp) |
268 | 0 | { |
269 | 0 | int stat = NCJ_OK; |
270 | 0 | int token = NCJ_UNDEF; |
271 | 0 | NCjson* element = NULL; |
272 | 0 | int stop = 0; |
273 | | |
274 | | /* [ ^e1,e2, ...en] */ |
275 | |
|
276 | 0 | while(!stop) { |
277 | | /* Recurse to get the value ei (might be null) */ |
278 | 0 | if((stat = NCJparseR(parser,&element))==NCJ_ERR) goto done; |
279 | 0 | token = NCJlex(parser); /* Get next token */ |
280 | | /* Next token should be comma or rbracket */ |
281 | 0 | switch(token) { |
282 | 0 | case NCJ_RBRACKET: |
283 | 0 | if(element != NULL) listappend(arrayp,element); |
284 | 0 | element = NULL; |
285 | 0 | stop = 1; |
286 | 0 | break; |
287 | 0 | case NCJ_COMMA: |
288 | | /* Append the ei to the list */ |
289 | 0 | if(element == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;} /* error */ |
290 | 0 | listappend(arrayp,element); |
291 | 0 | element = NULL; |
292 | 0 | break; |
293 | 0 | case NCJ_EOF: |
294 | 0 | case NCJ_UNDEF: |
295 | 0 | default: |
296 | 0 | stat = NCJTHROW(NCJ_ERR); |
297 | 0 | goto done; |
298 | 0 | } |
299 | 0 | } |
300 | | |
301 | 0 | done: |
302 | 0 | if(element != NULL) |
303 | 0 | NCJreclaim(element); |
304 | 0 | return NCJTHROW(stat); |
305 | 0 | } |
306 | | |
307 | | static int |
308 | | NCJparseDict(NCJparser* parser, struct NCjlist* dictp) |
309 | 0 | { |
310 | 0 | int stat = NCJ_OK; |
311 | 0 | int token = NCJ_UNDEF; |
312 | 0 | NCjson* value = NULL; |
313 | 0 | NCjson* key = NULL; |
314 | 0 | int stop = 0; |
315 | | |
316 | | /* { ^k1:v1,k2:v2, ...kn:vn] */ |
317 | |
|
318 | 0 | while(!stop) { |
319 | | /* Get the key, which must be a word of some sort */ |
320 | 0 | token = NCJlex(parser); |
321 | 0 | switch(token) { |
322 | 0 | case NCJ_STRING: |
323 | 0 | case NCJ_BOOLEAN: |
324 | 0 | case NCJ_INT: case NCJ_DOUBLE: { |
325 | 0 | if((stat=NCJnewstring(token,parser->yytext,&key))==NCJ_ERR) goto done; |
326 | 0 | } break; |
327 | 0 | case NCJ_RBRACE: /* End of containing Dict */ |
328 | 0 | stop = 1; |
329 | 0 | continue; /* leave loop */ |
330 | 0 | case NCJ_EOF: case NCJ_UNDEF: |
331 | 0 | default: |
332 | 0 | stat = NCJTHROW(NCJ_ERR); |
333 | 0 | goto done; |
334 | 0 | } |
335 | | /* Next token must be colon*/ |
336 | 0 | switch((token = NCJlex(parser))) { |
337 | 0 | case NCJ_COLON: break; |
338 | 0 | case NCJ_UNDEF: case NCJ_EOF: |
339 | 0 | default: stat = NCJTHROW(NCJ_ERR); goto done; |
340 | 0 | } |
341 | | /* Get the value */ |
342 | 0 | if((stat = NCJparseR(parser,&value))==NCJ_ERR) goto done; |
343 | | /* Next token must be comma or RBRACE */ |
344 | 0 | switch((token = NCJlex(parser))) { |
345 | 0 | case NCJ_RBRACE: |
346 | 0 | stop = 1; |
347 | | /* fall thru */ |
348 | 0 | case NCJ_COMMA: |
349 | | /* Insert key value into dict: key first, then value */ |
350 | 0 | listappend(dictp,key); |
351 | 0 | key = NULL; |
352 | 0 | listappend(dictp,value); |
353 | 0 | value = NULL; |
354 | 0 | break; |
355 | 0 | case NCJ_EOF: |
356 | 0 | case NCJ_UNDEF: |
357 | 0 | default: |
358 | 0 | stat = NCJTHROW(NCJ_ERR); |
359 | 0 | goto done; |
360 | 0 | } |
361 | 0 | } |
362 | | |
363 | 0 | done: |
364 | 0 | if(key != NULL) |
365 | 0 | NCJreclaim(key); |
366 | 0 | if(value != NULL) |
367 | 0 | NCJreclaim(value); |
368 | 0 | return NCJTHROW(stat); |
369 | 0 | } |
370 | | |
371 | | static int |
372 | | NCJlex(NCJparser* parser) |
373 | 0 | { |
374 | 0 | int token = NCJ_UNDEF; |
375 | 0 | char* start; |
376 | 0 | size_t count; |
377 | |
|
378 | 0 | while(token == 0) { /* avoid need to goto when retrying */ |
379 | 0 | char c = *parser->pos; |
380 | 0 | if(c == '\0') { |
381 | 0 | token = NCJ_EOF; |
382 | 0 | } else if(c <= ' ' || c == '\177') {/* ignore whitespace */ |
383 | 0 | parser->pos++; |
384 | 0 | continue; |
385 | 0 | } else if(c == NCJ_ESCAPE) { |
386 | 0 | parser->pos++; |
387 | 0 | c = *parser->pos; |
388 | 0 | *parser->pos = (char)unescape1(c); |
389 | 0 | continue; |
390 | 0 | } else if(strchr(JSON_WORD, c) != NULL) { |
391 | 0 | start = parser->pos; |
392 | 0 | for(;;) { |
393 | 0 | c = *parser->pos++; |
394 | 0 | if(c == '\0' || strchr(JSON_WORD,c) == NULL) break; /* end of word */ |
395 | 0 | } |
396 | | /* Pushback c */ |
397 | 0 | parser->pos--; |
398 | 0 | count = (size_t)((parser->pos) - start); |
399 | 0 | if(NCJyytext(parser,start,count)) goto done; |
400 | | /* Discriminate the word string to get the proper sort */ |
401 | 0 | if(testbool(parser->yytext)) |
402 | 0 | token = NCJ_BOOLEAN; |
403 | | /* do int test first since double subsumes int */ |
404 | 0 | else if(testint(parser->yytext)) |
405 | 0 | token = NCJ_INT; |
406 | 0 | else if(testdouble(parser->yytext)) |
407 | 0 | token = NCJ_DOUBLE; |
408 | 0 | else if(testnull(parser->yytext)) |
409 | 0 | token = NCJ_NULL; |
410 | 0 | else |
411 | 0 | token = NCJ_STRING; |
412 | 0 | } else if(c == NCJ_QUOTE) { |
413 | 0 | parser->pos++; |
414 | 0 | start = parser->pos; |
415 | 0 | for(;;) { |
416 | 0 | c = *parser->pos++; |
417 | 0 | if(c == NCJ_ESCAPE) parser->pos++; |
418 | 0 | else if(c == NCJ_QUOTE || c == '\0') break; |
419 | 0 | } |
420 | 0 | if(c == '\0') { |
421 | 0 | parser->status = NCJ_ERR; |
422 | 0 | token = NCJ_UNDEF; |
423 | 0 | goto done; |
424 | 0 | } |
425 | 0 | count = (size_t)((parser->pos) - start) - 1; /* -1 for trailing quote */ |
426 | 0 | if(NCJyytext(parser,start,count)==NCJ_ERR) goto done; |
427 | 0 | if(NCJunescape(parser)==NCJ_ERR) goto done; |
428 | 0 | token = NCJ_STRING; |
429 | 0 | } else { /* single char token */ |
430 | 0 | if(NCJyytext(parser,parser->pos,1)==NCJ_ERR) goto done; |
431 | 0 | token = *parser->pos++; |
432 | 0 | } |
433 | | #ifdef NCJDEBUG |
434 | | fprintf(stderr,"%s(%d): |%s|\n",tokenname(token),token,parser->yytext); |
435 | | #endif /*NCJDEBUG*/ |
436 | 0 | } /*for(;;)*/ |
437 | 0 | done: |
438 | 0 | if(parser->status == NCJ_ERR) |
439 | 0 | token = NCJ_UNDEF; |
440 | | #ifdef NCJTRACE |
441 | | if(parser->flags & NCJ_TRACE) { |
442 | | const char* txt = NULL; |
443 | | switch(token) { |
444 | | case NCJ_STRING: case NCJ_INT: case NCJ_DOUBLE: case NCJ_BOOLEAN: txt = parser->yytext; break; |
445 | | default: break; |
446 | | } |
447 | | fprintf(stderr,">>>> token=%s:'%s'\n",tokenname(token),(txt?txt:"")); |
448 | | } |
449 | | #endif /*NCJTRACE*/ |
450 | 0 | return token; |
451 | 0 | } |
452 | | |
453 | | static int |
454 | | testnull(const char* word) |
455 | 0 | { |
456 | 0 | if(strcasecmp(word,NCJ_TAG_NULL)==0) return 1; |
457 | 0 | return 0; |
458 | 0 | } |
459 | | |
460 | | static int |
461 | | testbool(const char* word) |
462 | 0 | { |
463 | 0 | if(strcasecmp(word,NCJ_TAG_TRUE)==0 |
464 | 0 | || strcasecmp(word,NCJ_TAG_FALSE)==0) |
465 | 0 | return 1; |
466 | 0 | return 0; |
467 | 0 | } |
468 | | |
469 | | static int |
470 | | testint(const char* word) |
471 | 0 | { |
472 | 0 | int ncvt; |
473 | 0 | long long i; |
474 | 0 | int count = 0; |
475 | | /* Try to convert to number */ |
476 | 0 | ncvt = sscanf(word,"%lld%n",&i,&count); |
477 | 0 | return (ncvt == 1 && strlen(word)==((size_t)count) ? 1 : 0); |
478 | 0 | } |
479 | | |
480 | | static int |
481 | | nancmp(const void* keyp, const void* membpp) |
482 | 0 | { |
483 | 0 | int cmp; |
484 | 0 | const char* key = (const char*)keyp; |
485 | 0 | const char** membp = (const char**)membpp; |
486 | 0 | cmp = strcasecmp(key,*membp); |
487 | 0 | return cmp; |
488 | 0 | } |
489 | | |
490 | | static int |
491 | | testdouble(const char* word) |
492 | 0 | { |
493 | 0 | int ncvt; |
494 | 0 | double d; |
495 | 0 | int count = 0; |
496 | 0 | void* pos = NULL; |
497 | | |
498 | | /* Check for Nan and Infinity */ |
499 | 0 | pos = bsearch(word, NANINF, NNANINF, sizeof(char*), nancmp); |
500 | 0 | if(pos != NULL) return 1; |
501 | | /* Try to convert to number */ |
502 | 0 | ncvt = sscanf(word,"%lg%n",&d,&count); |
503 | 0 | return (ncvt == 1 && strlen(word)==((size_t)count) ? 1 : 0); |
504 | 0 | } |
505 | | |
506 | | static int |
507 | | NCJyytext(NCJparser* parser, char* start, size_t pdlen) |
508 | 0 | { |
509 | 0 | size_t len = (size_t)pdlen; |
510 | 0 | if(parser->yytext == NULL) { |
511 | 0 | parser->yytext = (char*)malloc(len+1); |
512 | 0 | parser->yylen = len; |
513 | 0 | } else if(parser->yylen <= len) { |
514 | 0 | parser->yytext = (char*) realloc(parser->yytext,len+1); |
515 | 0 | parser->yylen = len; |
516 | 0 | } |
517 | 0 | if(parser->yytext == NULL) return NCJTHROW(NCJ_ERR); |
518 | 0 | memcpy(parser->yytext,start,len); |
519 | 0 | parser->yytext[len] = '\0'; |
520 | 0 | return NCJTHROW(NCJ_OK); |
521 | 0 | } |
522 | | |
523 | | /**************************************************/ |
524 | | |
525 | | OPTSTATIC void |
526 | | NCJreclaim(NCjson* json) |
527 | 0 | { |
528 | 0 | if(json == NULL) return; |
529 | 0 | switch(json->sort) { |
530 | 0 | case NCJ_INT: |
531 | 0 | case NCJ_DOUBLE: |
532 | 0 | case NCJ_BOOLEAN: |
533 | 0 | case NCJ_STRING: |
534 | 0 | nullfree(json->string); |
535 | 0 | break; |
536 | 0 | case NCJ_DICT: |
537 | 0 | NCJreclaimDict(&json->list); |
538 | 0 | break; |
539 | 0 | case NCJ_ARRAY: |
540 | 0 | NCJreclaimArray(&json->list); |
541 | 0 | break; |
542 | 0 | default: break; /* nothing to reclaim */ |
543 | 0 | } |
544 | 0 | free(json); |
545 | 0 | } |
546 | | |
547 | | static void |
548 | | NCJreclaimArray(struct NCjlist* array) |
549 | 0 | { |
550 | 0 | size_t i; |
551 | 0 | for(i=0;i<array->len;i++) { |
552 | 0 | NCJreclaim(array->contents[i]); |
553 | 0 | } |
554 | 0 | nullfree(array->contents); |
555 | 0 | array->contents = NULL; |
556 | 0 | array->len = 0; |
557 | 0 | } |
558 | | |
559 | | static void |
560 | | NCJreclaimDict(struct NCjlist* dict) |
561 | 0 | { |
562 | 0 | NCJreclaimArray(dict); |
563 | 0 | } |
564 | | |
565 | | /**************************************************/ |
566 | | /* Build Functions */ |
567 | | |
568 | | OPTSTATIC int |
569 | | NCJnew(int sort, NCjson** objectp) |
570 | 0 | { |
571 | 0 | int stat = NCJ_OK; |
572 | 0 | NCjson* object = NULL; |
573 | |
|
574 | 0 | if((object = (NCjson*)calloc(1,sizeof(NCjson))) == NULL) |
575 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
576 | 0 | NCJsetsort(object,sort); |
577 | 0 | switch (sort) { |
578 | 0 | case NCJ_INT: |
579 | 0 | case NCJ_DOUBLE: |
580 | 0 | case NCJ_BOOLEAN: |
581 | 0 | case NCJ_STRING: |
582 | 0 | case NCJ_NULL: |
583 | 0 | break; |
584 | 0 | case NCJ_DICT: |
585 | 0 | case NCJ_ARRAY: |
586 | 0 | break; |
587 | 0 | default: |
588 | 0 | stat = NCJTHROW(NCJ_ERR); |
589 | 0 | goto done; |
590 | 0 | } |
591 | 0 | if(objectp) {*objectp = object; object = NULL;} |
592 | |
|
593 | 0 | done: |
594 | 0 | if(stat) NCJreclaim(object); |
595 | 0 | return NCJTHROW(stat); |
596 | 0 | } |
597 | | |
598 | | OPTSTATIC int |
599 | | NCJnewstring(int sort, const char* value, NCjson** jsonp) |
600 | 0 | { |
601 | 0 | return NCJTHROW(NCJnewstringn(sort,strlen(value),value,jsonp)); |
602 | 0 | } |
603 | | |
604 | | OPTSTATIC int |
605 | | NCJnewstringn(int sort, size_t len, const char* value, NCjson** jsonp) |
606 | 0 | { |
607 | 0 | int stat = NCJ_OK; |
608 | 0 | NCjson* json = NULL; |
609 | |
|
610 | 0 | if(jsonp) *jsonp = NULL; |
611 | 0 | if(value == NULL) |
612 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
613 | 0 | if((stat = NCJnew(sort,&json))==NCJ_ERR) goto done; |
614 | 0 | if((json->string = (char*)malloc(len+1))==NULL) |
615 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
616 | 0 | memcpy(json->string,value,len); |
617 | 0 | json->string[len] = '\0'; |
618 | 0 | if(jsonp) *jsonp = json; |
619 | 0 | json = NULL; /* avoid memory errors */ |
620 | 0 | done: |
621 | 0 | NCJreclaim(json); |
622 | 0 | return NCJTHROW(stat); |
623 | 0 | } |
624 | | |
625 | | OPTSTATIC int |
626 | | NCJdictget(const NCjson* dict, const char* key, const NCjson** jvaluep) |
627 | 0 | { |
628 | 0 | int stat = NCJ_OK; |
629 | 0 | size_t i; |
630 | |
|
631 | 0 | if(dict == NULL || dict->sort != NCJ_DICT) |
632 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
633 | 0 | if(jvaluep) {*jvaluep = NULL;} |
634 | 0 | for(i=0;i<NCJdictlength(dict);i++) { |
635 | 0 | NCjson* jkey = NCJdictkey(dict,i); |
636 | 0 | NCjson* jvalue = NCJdictvalue(dict,i); |
637 | 0 | if(jkey->string != NULL && strcmp(jkey->string,key)==0) { |
638 | 0 | if(jvaluep) {*jvaluep = jvalue;} |
639 | 0 | break; |
640 | 0 | } |
641 | 0 | } |
642 | |
|
643 | 0 | done: |
644 | 0 | return NCJTHROW(stat); |
645 | 0 | } |
646 | | |
647 | | /* Functional version of NCJdictget */ |
648 | | OPTSTATIC NCjson* |
649 | | NCJdictlookup(const NCjson* dict, const char* key) |
650 | 0 | { |
651 | 0 | int stat; |
652 | 0 | const NCjson* jvalue = NULL; |
653 | 0 | stat = NCJdictget(dict,key,&jvalue); |
654 | 0 | if(stat != NCJ_OK) jvalue = NULL; |
655 | 0 | return jvalue; |
656 | 0 | } |
657 | | |
658 | | /* Unescape the text in parser->yytext; can |
659 | | do in place because unescaped string will |
660 | | always be shorter */ |
661 | | static int |
662 | | NCJunescape(NCJparser* parser) |
663 | 0 | { |
664 | 0 | char* p = parser->yytext; |
665 | 0 | char* q = p; |
666 | 0 | char c; |
667 | 0 | for(;(c=*p++);) { |
668 | 0 | if(c == NCJ_ESCAPE) { |
669 | 0 | c = *p++; |
670 | 0 | switch (c) { |
671 | 0 | case 'b': c = '\b'; break; |
672 | 0 | case 'f': c = '\f'; break; |
673 | 0 | case 'n': c = '\n'; break; |
674 | 0 | case 'r': c = '\r'; break; |
675 | 0 | case 't': c = '\t'; break; |
676 | 0 | case NCJ_QUOTE: break; |
677 | 0 | case NCJ_ESCAPE: break; |
678 | 0 | default: break;/* technically not Json conformant */ |
679 | 0 | } |
680 | 0 | } |
681 | 0 | *q++ = (char)c; |
682 | 0 | } |
683 | 0 | *q = '\0'; |
684 | 0 | return NCJTHROW(NCJ_OK); |
685 | 0 | } |
686 | | |
687 | | /* Unescape a single character */ |
688 | | static char |
689 | | unescape1(char c) |
690 | 0 | { |
691 | 0 | switch (c) { |
692 | 0 | case 'b': c = '\b'; break; |
693 | 0 | case 'f': c = '\f'; break; |
694 | 0 | case 'n': c = '\n'; break; |
695 | 0 | case 'r': c = '\r'; break; |
696 | 0 | case 't': c = '\t'; break; |
697 | 0 | default: break;/* technically not Json conformant */ |
698 | 0 | } |
699 | 0 | return c; |
700 | 0 | } |
701 | | |
702 | | #if defined NCJDEBUG || defined NCJTRACE |
703 | | static char* |
704 | | tokenname(int token) |
705 | | { |
706 | | switch (token) { |
707 | | case NCJ_STRING: return ("NCJ_STRING"); |
708 | | case NCJ_INT: return ("NCJ_INT"); |
709 | | case NCJ_DOUBLE: return ("NCJ_DOUBLE"); |
710 | | case NCJ_BOOLEAN: return ("NCJ_BOOLEAN"); |
711 | | case NCJ_DICT: return ("NCJ_DICT"); |
712 | | case NCJ_ARRAY: return ("NCJ_ARRAY"); |
713 | | case NCJ_NULL: return ("NCJ_NULL"); |
714 | | default: |
715 | | if(token > ' ' && token <= 127) { |
716 | | static char s[4]; |
717 | | s[0] = '\''; |
718 | | s[1] = (char)token; |
719 | | s[2] = '\''; |
720 | | s[3] = '\0'; |
721 | | return (s); |
722 | | } else |
723 | | break; |
724 | | } |
725 | | return ("NCJ_UNDEF"); |
726 | | } |
727 | | #endif /*defined NCJDEBUG || defined NCJTRACE*/ |
728 | | |
729 | | /* Convert a JSON value to an equivalent value of a specified sort */ |
730 | | OPTSTATIC int |
731 | | NCJcvt(const NCjson* jvalue, int outsort, struct NCJconst* output) |
732 | 0 | { |
733 | 0 | int stat = NCJ_OK; |
734 | |
|
735 | 0 | if(output == NULL) goto done; |
736 | | |
737 | 0 | #undef CASE |
738 | 0 | #define CASE(t1,t2) ((t1)<<4 | (t2)) /* the shift constant must be larger than log2(NCJ_NSORTS) */ |
739 | 0 | switch (CASE(jvalue->sort,outsort)) { |
740 | | |
741 | 0 | case CASE(NCJ_BOOLEAN,NCJ_BOOLEAN): |
742 | 0 | if(strcasecmp(jvalue->string,NCJ_TAG_FALSE)==0) output->bval = 0; else output->bval = 1; |
743 | 0 | break; |
744 | 0 | case CASE(NCJ_BOOLEAN,NCJ_INT): |
745 | 0 | if(strcasecmp(jvalue->string,NCJ_TAG_FALSE)==0) output->ival = 0; else output->ival = 1; |
746 | 0 | break; |
747 | 0 | case CASE(NCJ_BOOLEAN,NCJ_DOUBLE): |
748 | 0 | if(strcasecmp(jvalue->string,NCJ_TAG_FALSE)==0) output->dval = 0.0; else output->dval = 1.0; |
749 | 0 | break; |
750 | 0 | case CASE(NCJ_BOOLEAN,NCJ_STRING): |
751 | 0 | output->sval = nulldup(jvalue->string); |
752 | 0 | break; |
753 | | |
754 | 0 | case CASE(NCJ_INT,NCJ_BOOLEAN): |
755 | 0 | sscanf(jvalue->string,"%lld",&output->ival); |
756 | 0 | output->bval = (output->ival?1:0); |
757 | 0 | break; |
758 | 0 | case CASE(NCJ_INT,NCJ_INT): |
759 | 0 | sscanf(jvalue->string,"%lld",&output->ival); |
760 | 0 | break; |
761 | 0 | case CASE(NCJ_INT,NCJ_DOUBLE): |
762 | 0 | sscanf(jvalue->string,"%lld",&output->ival); |
763 | 0 | output->dval = (double)output->ival; |
764 | 0 | break; |
765 | 0 | case CASE(NCJ_INT,NCJ_STRING): |
766 | 0 | output->sval = nulldup(jvalue->string); |
767 | 0 | break; |
768 | | |
769 | 0 | case CASE(NCJ_DOUBLE,NCJ_BOOLEAN): |
770 | 0 | sscanf(jvalue->string,"%lf",&output->dval); |
771 | 0 | output->bval = (output->dval == 0?0:1); |
772 | 0 | break; |
773 | 0 | case CASE(NCJ_DOUBLE,NCJ_INT): |
774 | 0 | sscanf(jvalue->string,"%lf",&output->dval); |
775 | 0 | output->ival = (long long)output->dval; |
776 | 0 | break; |
777 | 0 | case CASE(NCJ_DOUBLE,NCJ_DOUBLE): |
778 | 0 | sscanf(jvalue->string,"%lf",&output->dval); |
779 | 0 | break; |
780 | 0 | case CASE(NCJ_DOUBLE,NCJ_STRING): |
781 | 0 | output->sval = nulldup(jvalue->string); |
782 | 0 | break; |
783 | | |
784 | 0 | case CASE(NCJ_STRING,NCJ_BOOLEAN): |
785 | 0 | if(strcasecmp(jvalue->string,NCJ_TAG_FALSE)==0) output->bval = 0; else output->bval = 1; |
786 | 0 | break; |
787 | 0 | case CASE(NCJ_STRING,NCJ_INT): |
788 | 0 | sscanf(jvalue->string,"%lld",&output->ival); |
789 | 0 | break; |
790 | 0 | case CASE(NCJ_STRING,NCJ_DOUBLE): |
791 | 0 | sscanf(jvalue->string,"%lf",&output->dval); |
792 | 0 | break; |
793 | 0 | case CASE(NCJ_STRING,NCJ_STRING): |
794 | 0 | output->sval = nulldup(jvalue->string); |
795 | 0 | break; |
796 | | |
797 | 0 | default: |
798 | 0 | stat = NCJTHROW(NCJ_ERR); |
799 | 0 | break; |
800 | 0 | } |
801 | | |
802 | 0 | done: |
803 | 0 | return NCJTHROW(stat); |
804 | 0 | } |
805 | | |
806 | | static int |
807 | | listappend(struct NCjlist* list, NCjson* json) |
808 | 0 | { |
809 | 0 | int stat = NCJ_OK; |
810 | |
|
811 | 0 | assert(list->len == 0 || list->contents != NULL); |
812 | 0 | if(json == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;} |
813 | | /* Make space for two new elements; better than one, but still probably not optimal */ |
814 | 0 | if((stat = listsetalloc(list,list->len + 2))<0) goto done; |
815 | | /* Append the new item */ |
816 | 0 | list->contents[list->len++] = json; |
817 | |
|
818 | 0 | done: |
819 | 0 | return NCJTHROW(stat); |
820 | 0 | } |
821 | | |
822 | | /* Locate the index of a key in a list of (key,value) pairs. |
823 | | @param list pointer to the list |
824 | | @param key for which to search |
825 | | @param indexp store index of match here |
826 | | @return NCJ_OK if key found, NCJ_EOF if not found, NCJ_ERR if error |
827 | | */ |
828 | | static int |
829 | | listlookup(const struct NCjlist* list, const char* key, size_t* indexp) |
830 | 0 | { |
831 | 0 | int stat = NCJ_OK; |
832 | 0 | int i,len,match = -1; |
833 | |
|
834 | 0 | if(list == NULL || key == NULL || strlen(key) == 0 || list->len %2 == 1) |
835 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
836 | 0 | len = (int)list->len; /* => |list| < 2 billion or do */ |
837 | 0 | for(i=0;i<len;i+=2) { |
838 | 0 | const NCjson* jkey = list->contents[i]; |
839 | 0 | if(jkey != NULL && jkey->string != NULL && strcmp(jkey->string,key)==0) {match = i;break;} |
840 | 0 | } |
841 | 0 | if(match < 0) {stat = NCJ_EOF;} else {if(indexp) *indexp = (size_t)match;} |
842 | 0 | done: |
843 | 0 | return NCJTHROW(stat); |
844 | 0 | } |
845 | | |
846 | | /* Increase the space available to dict/array. |
847 | | Even if alloc is zero, ensure that the object's list alloc is >= 1. |
848 | | @param list pointer to the list |
849 | | @param alloc increase allocation to this size |
850 | | @return NCJ_ERR|NCJ_OK |
851 | | */ |
852 | | static int |
853 | | listsetalloc(struct NCjlist* list, size_t alloc) |
854 | 0 | { |
855 | 0 | int stat = NCJ_OK; |
856 | 0 | NCjson** newcontents = NULL; |
857 | |
|
858 | 0 | if(list == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;} |
859 | 0 | assert(list->alloc == 0 || list->contents != NULL); |
860 | 0 | if(alloc == 0) alloc = 1; /* Guarantee that the list->content is not NULL */ |
861 | 0 | if(list->alloc >= alloc) goto done; |
862 | | /* Since alloc > list->alloc > 0, we need to allocate space */ |
863 | 0 | if((newcontents=(NCjson**)calloc(alloc,sizeof(NCjson*))) == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;} |
864 | 0 | list->alloc = alloc; |
865 | 0 | if(list->contents != NULL && list->len > 0) { |
866 | | /* Preserve any existing contents */ |
867 | 0 | memcpy((void*)newcontents, |
868 | 0 | (void*)list->contents, |
869 | 0 | sizeof(NCjson*)*list->len); |
870 | 0 | } |
871 | 0 | free(list->contents); |
872 | 0 | list->contents = newcontents; newcontents = NULL; |
873 | 0 | assert(list->alloc > 0 && list->contents != NULL); |
874 | 0 | done: |
875 | 0 | if(newcontents != NULL) free(newcontents); |
876 | 0 | return NCJTHROW(stat); |
877 | 0 | } |
878 | | |
879 | | /**************************************************/ |
880 | | |
881 | | OPTSTATIC int |
882 | | NCJclone(const NCjson* json, NCjson** clonep) |
883 | 0 | { |
884 | 0 | int stat = NCJ_OK; |
885 | 0 | NCjson* clone = NULL; |
886 | |
|
887 | 0 | if(clonep) *clonep = NULL; |
888 | 0 | if(json == NULL) goto done; |
889 | 0 | switch(NCJsort(json)) { |
890 | 0 | case NCJ_INT: |
891 | 0 | case NCJ_DOUBLE: |
892 | 0 | case NCJ_BOOLEAN: |
893 | 0 | case NCJ_STRING: |
894 | 0 | if((stat=NCJnew(NCJsort(json),&clone))==NCJ_ERR) goto done; |
895 | 0 | NCJsetstring(clone,strdup(NCJstring(json))); |
896 | 0 | if(NCJstring(clone)==NULL) |
897 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
898 | 0 | break; |
899 | 0 | case NCJ_NULL: |
900 | 0 | if((stat=NCJnew(NCJsort(json),&clone))==NCJ_ERR) goto done; |
901 | 0 | break; |
902 | 0 | case NCJ_DICT: |
903 | 0 | if((stat=NCJcloneDict(json,&clone))==NCJ_ERR) goto done; |
904 | 0 | break; |
905 | 0 | case NCJ_ARRAY: |
906 | 0 | if((stat=NCJcloneArray(json,&clone))==NCJ_ERR) goto done; |
907 | 0 | break; |
908 | 0 | default: break; /* nothing to clone */ |
909 | 0 | } |
910 | 0 | done: |
911 | 0 | if(stat == NCJ_OK && clonep) {*clonep = clone; clone = NULL;} |
912 | 0 | NCJreclaim(clone); |
913 | 0 | return NCJTHROW(stat); |
914 | 0 | } |
915 | | |
916 | | static int |
917 | | NCJcloneArray(const NCjson* array, NCjson** clonep) |
918 | 0 | { |
919 | 0 | int stat=NCJ_OK; |
920 | 0 | size_t i; |
921 | 0 | NCjson* clone = NULL; |
922 | 0 | if((stat=NCJnew(NCJ_ARRAY,&clone))==NCJ_ERR) goto done; |
923 | 0 | if((stat=listsetalloc(&clone->list,array->list.len))<0) goto done; |
924 | 0 | for(i=0;i<NCJarraylength(array);i++) { |
925 | 0 | NCjson* elem = NCJith(array,i); |
926 | 0 | NCjson* elemclone = NULL; |
927 | 0 | if((stat=NCJclone(elem,&elemclone))==NCJ_ERR) goto done; |
928 | 0 | NCJappend(clone,elemclone); |
929 | 0 | } |
930 | 0 | done: |
931 | 0 | if(stat == NCJ_OK && clonep) {*clonep = clone; clone = NULL;} |
932 | 0 | NCJreclaim(clone); |
933 | 0 | return stat; |
934 | 0 | } |
935 | | |
936 | | static int |
937 | | NCJcloneDict(const NCjson* dict, NCjson** clonep) |
938 | 0 | { |
939 | 0 | int stat=NCJ_OK; |
940 | 0 | size_t i; |
941 | 0 | NCjson* clone = NULL; |
942 | 0 | if((stat=NCJnew(NCJ_DICT,&clone))==NCJ_ERR) goto done; |
943 | 0 | if((stat=listsetalloc(&clone->list,dict->list.len))<0) goto done; |
944 | 0 | for(i=0;i<NCJarraylength(dict);i++) { |
945 | 0 | NCjson* elem = NCJith(dict,i); |
946 | 0 | NCjson* elemclone = NULL; |
947 | 0 | if((stat=NCJclone(elem,&elemclone))==NCJ_ERR) goto done; |
948 | 0 | NCJappend(clone,elemclone); |
949 | 0 | } |
950 | 0 | done: |
951 | 0 | if(stat == NCJ_OK && clonep) {*clonep = clone; clone = NULL;} |
952 | 0 | NCJreclaim(clone); |
953 | 0 | return NCJTHROW(stat); |
954 | 0 | } |
955 | | |
956 | | OPTSTATIC int |
957 | | NCJaddstring(NCjson* json, int sort, const char* s) |
958 | 0 | { |
959 | 0 | int stat = NCJ_OK; |
960 | 0 | NCjson* jtmp = NULL; |
961 | |
|
962 | 0 | if(NCJsort(json) != NCJ_DICT && NCJsort(json) != NCJ_ARRAY) |
963 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
964 | 0 | if((stat = NCJnewstring(sort, s, &jtmp))==NCJ_ERR) goto done; |
965 | 0 | if((stat = NCJappend(json,jtmp))==NCJ_ERR) goto done; |
966 | 0 | jtmp = NULL; |
967 | | |
968 | 0 | done: |
969 | 0 | NCJreclaim(jtmp); |
970 | 0 | return NCJTHROW(stat); |
971 | 0 | } |
972 | | |
973 | | /* Insert key-value pair into a dict object. key will be strdup'd, jvalue will be used without copying. |
974 | | If key already exists, then it's value is overwritten |
975 | | */ |
976 | | OPTSTATIC int |
977 | | NCJinsert(NCjson* jdict, const char* key, NCjson* jvalue) |
978 | 0 | { |
979 | 0 | int stat = NCJ_OK; |
980 | 0 | size_t i; |
981 | 0 | NCjson* jkey = NULL; |
982 | 0 | NCjson* jprev = NULL; |
983 | 0 | int found; |
984 | |
|
985 | 0 | if(jdict == NULL |
986 | 0 | || NCJsort(jdict) != NCJ_DICT |
987 | 0 | || key == NULL |
988 | 0 | || jvalue == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;} |
989 | 0 | for(found=(-1),i=0;i < NCJdictlength(jdict); i++) { |
990 | 0 | jkey = NCJdictkey(jdict,i); |
991 | 0 | if (jkey != NULL && strcmp(NCJstring(jkey), key) == 0) { |
992 | 0 | found = (int)i; |
993 | 0 | break; |
994 | 0 | } |
995 | 0 | } |
996 | 0 | if(found >= 0) { |
997 | 0 | jprev = NCJdictvalue(jdict,found); |
998 | | // replace existing values for new key |
999 | 0 | NCJreclaim(jprev); // free old value |
1000 | 0 | NCJdictvalue(jdict,found) = jvalue; jvalue = NULL; |
1001 | 0 | jkey = NULL; /* avoid reclamation */ |
1002 | 0 | } else { /* not found */ |
1003 | 0 | if((stat=listsetalloc(&jdict->list,jdict->list.len+2))<0) goto done; |
1004 | 0 | NCJcheck(NCJnewstring(NCJ_STRING, key, (NCjson**)&jkey)); |
1005 | 0 | NCJcheck(NCJappend(jdict,jkey)); jkey = NULL; |
1006 | 0 | NCJcheck(NCJappend(jdict,jvalue)); jvalue = NULL; |
1007 | 0 | } |
1008 | 0 | done: |
1009 | 0 | NCJreclaim(jkey); |
1010 | 0 | NCJreclaim(jvalue); |
1011 | 0 | return NCJTHROW(stat); |
1012 | 0 | } |
1013 | | |
1014 | | /* Insert key-value pair into a dict object. key will be strdup'd */ |
1015 | | OPTSTATIC int |
1016 | | NCJinsertstring(NCjson* object, const char* key, const char* value) |
1017 | 0 | { |
1018 | 0 | int stat = NCJ_OK; |
1019 | 0 | NCjson* jkey = NULL; |
1020 | 0 | NCjson* jvalue = NULL; |
1021 | 0 | if(key == NULL || value == NULL) |
1022 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
1023 | 0 | if((stat = NCJnewstring(NCJ_STRING,key,&jkey))==NCJ_ERR) goto done; |
1024 | 0 | if((stat = NCJnewstring(NCJ_STRING,value,&jvalue))==NCJ_ERR) goto done; |
1025 | 0 | if((stat = NCJappend(object,jkey))==NCJ_ERR) goto done; |
1026 | 0 | if((stat = NCJappend(object,jvalue))==NCJ_ERR) goto done; |
1027 | 0 | done: |
1028 | 0 | return NCJTHROW(stat); |
1029 | 0 | } |
1030 | | |
1031 | | /* Insert key-value pair into a dict object. key will be strdup'd */ |
1032 | | OPTSTATIC int |
1033 | | NCJinsertint(NCjson* object, const char* key, long long value) |
1034 | 0 | { |
1035 | 0 | int stat = NCJ_OK; |
1036 | 0 | NCjson* jkey = NULL; |
1037 | 0 | NCjson* jvalue = NULL; |
1038 | 0 | char digits[64]; |
1039 | | |
1040 | 0 | if(key == NULL) |
1041 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
1042 | 0 | if((stat = NCJnewstring(NCJ_STRING,key,&jkey))==NCJ_ERR) goto done; |
1043 | 0 | snprintf(digits,sizeof(digits),"%lld",value); |
1044 | 0 | if((stat = NCJnewstring(NCJ_INT,digits,&jvalue))==NCJ_ERR) goto done; |
1045 | 0 | listsetalloc(&object->list,object->list.len + 2); |
1046 | 0 | if((stat = NCJappend(object,jkey))==NCJ_ERR) goto done; |
1047 | 0 | if((stat = NCJappend(object,jvalue))==NCJ_ERR) goto done; |
1048 | 0 | done: |
1049 | 0 | return NCJTHROW(stat); |
1050 | 0 | } |
1051 | | |
1052 | | /* Append value to an array or dict object. */ |
1053 | | OPTSTATIC int |
1054 | | NCJappend(NCjson* object, NCjson* value) |
1055 | 0 | { |
1056 | 0 | if(object == NULL || value == NULL) |
1057 | 0 | return NCJTHROW(NCJ_ERR); |
1058 | 0 | switch (object->sort) { |
1059 | 0 | case NCJ_ARRAY: |
1060 | 0 | case NCJ_DICT: |
1061 | 0 | listappend(&object->list,value); |
1062 | 0 | break; |
1063 | 0 | default: |
1064 | 0 | return NCJTHROW(NCJ_ERR); |
1065 | 0 | } |
1066 | 0 | return NCJTHROW(NCJ_OK); |
1067 | 0 | } |
1068 | | |
1069 | | /* Append string value to an array or dict object. */ |
1070 | | OPTSTATIC int |
1071 | | NCJappendstring(NCjson* object, int sort, const char* s) |
1072 | 0 | { |
1073 | 0 | NCjson* js = NULL; |
1074 | 0 | if(object == NULL || s == NULL) |
1075 | 0 | return NCJTHROW(NCJ_ERR); |
1076 | 0 | NCJnewstring(sort,s,&js); |
1077 | 0 | switch (object->sort) { |
1078 | 0 | case NCJ_ARRAY: |
1079 | 0 | case NCJ_DICT: |
1080 | 0 | listappend(&object->list,js); |
1081 | 0 | break; |
1082 | 0 | default: |
1083 | 0 | return NCJTHROW(NCJ_ERR); |
1084 | 0 | } |
1085 | 0 | return NCJTHROW(NCJ_OK); |
1086 | 0 | } |
1087 | | |
1088 | | /* Append int value into an array/dict object. */ |
1089 | | OPTSTATIC int |
1090 | | NCJappendint(NCjson* object, long long value) |
1091 | 0 | { |
1092 | 0 | int stat = NCJ_OK; |
1093 | 0 | NCjson* jvalue = NULL; |
1094 | 0 | char digits[64]; |
1095 | | |
1096 | 0 | snprintf(digits,sizeof(digits),"%lld",value); |
1097 | 0 | NCJcheck(NCJnewstring(NCJ_INT,digits,&jvalue)); |
1098 | 0 | NCJcheck(NCJappend(object,jvalue)); jvalue = NULL; |
1099 | 0 | done: |
1100 | 0 | NCJreclaim(jvalue); |
1101 | 0 | return NCJTHROW(stat); |
1102 | 0 | } |
1103 | | |
1104 | | /* Overwrite key-value pair in a dict object. |
1105 | | If key does not exist, then act like NCJinsert(). |
1106 | | */ |
1107 | | OPTSTATIC int |
1108 | | NCJoverwrite(NCjson* dict, const char* key, NCjson* jvalue) |
1109 | 0 | { |
1110 | 0 | int stat = NCJ_OK; |
1111 | 0 | size_t index; |
1112 | 0 | NCjson* jkey = NULL; |
1113 | 0 | NCjson* oldvalue = NULL; |
1114 | |
|
1115 | 0 | if(dict == NULL |
1116 | 0 | || dict->sort != NCJ_DICT |
1117 | 0 | || key == NULL |
1118 | 0 | || jvalue == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;} |
1119 | | /* See if key already exists */ |
1120 | 0 | switch(stat=listlookup(&dict->list,key,&index)) { |
1121 | 0 | case NCJ_OK: |
1122 | | /* Overwrite value part */ |
1123 | 0 | oldvalue = dict->list.contents[index]; |
1124 | 0 | dict->list.contents[index] = jvalue; |
1125 | 0 | NCJreclaim(oldvalue); |
1126 | 0 | break; |
1127 | 0 | case NCJ_EOF: /* Not found */ |
1128 | 0 | if((stat=listsetalloc(&dict->list,dict->list.len+2))<0) goto done; |
1129 | 0 | if((stat = NCJappend(dict,jkey))==NCJ_ERR) goto done; |
1130 | 0 | if((stat = NCJappend(dict,jvalue))==NCJ_ERR) goto done; |
1131 | 0 | break; |
1132 | 0 | case NCJ_ERR: |
1133 | 0 | default: goto done; |
1134 | 0 | } |
1135 | 0 | done: |
1136 | 0 | return NCJTHROW(stat); |
1137 | 0 | } |
1138 | | |
1139 | | /**************************************************/ |
1140 | | /* Unparser to convert NCjson object to text in buffer */ |
1141 | | |
1142 | | OPTSTATIC int |
1143 | | NCJunparse(const NCjson* json, unsigned flags, char** textp) |
1144 | 0 | { |
1145 | 0 | int stat = NCJ_OK; |
1146 | 0 | NCJbuf buf = {0,NULL}; |
1147 | 0 | if((stat = NCJunparseR(json,&buf,flags))==NCJ_ERR) |
1148 | 0 | goto done; |
1149 | 0 | if(textp) {*textp = buf.text; buf.text = NULL; buf.len = 0;} |
1150 | 0 | done: |
1151 | 0 | nullfree(buf.text); |
1152 | 0 | return NCJTHROW(stat); |
1153 | 0 | } |
1154 | | |
1155 | | static int |
1156 | | NCJunparseR(const NCjson* json, NCJbuf* buf, unsigned flags) |
1157 | 0 | { |
1158 | 0 | int stat = NCJ_OK; |
1159 | 0 | size_t i; |
1160 | |
|
1161 | 0 | switch (NCJsort(json)) { |
1162 | 0 | case NCJ_STRING: |
1163 | 0 | bytesappendquoted(buf,json->string); |
1164 | 0 | break; |
1165 | 0 | case NCJ_INT: |
1166 | 0 | case NCJ_DOUBLE: |
1167 | 0 | case NCJ_BOOLEAN: |
1168 | 0 | bytesappend(buf,json->string); |
1169 | 0 | break; |
1170 | 0 | case NCJ_DICT: |
1171 | 0 | bytesappendc(buf,NCJ_LBRACE); |
1172 | 0 | if(json->list.len > 0 && json->list.contents != NULL) { |
1173 | 0 | int shortlist = 0; |
1174 | 0 | for(i=0;!shortlist && i < json->list.len;i+=2) { |
1175 | 0 | if(i > 0) {bytesappendc(buf,NCJ_COMMA);bytesappendc(buf,' ');}; |
1176 | 0 | NCJunparseR(json->list.contents[i],buf,flags); /* key */ |
1177 | 0 | bytesappendc(buf,NCJ_COLON); |
1178 | 0 | bytesappendc(buf,' '); |
1179 | | /* Allow for the possibility of a short dict entry */ |
1180 | 0 | if(json->list.contents[i+1] == NULL) { /* short */ |
1181 | 0 | bytesappendc(buf,'?'); |
1182 | 0 | shortlist = 1; |
1183 | 0 | } else { |
1184 | 0 | NCJunparseR(json->list.contents[i+1],buf,flags); |
1185 | 0 | } |
1186 | 0 | } |
1187 | 0 | } |
1188 | 0 | bytesappendc(buf,NCJ_RBRACE); |
1189 | 0 | break; |
1190 | 0 | case NCJ_ARRAY: |
1191 | 0 | bytesappendc(buf,NCJ_LBRACKET); |
1192 | 0 | if(json->list.len > 0 && json->list.contents != NULL) { |
1193 | 0 | for(i=0;i < json->list.len;i++) { |
1194 | 0 | if(i > 0) bytesappendc(buf,NCJ_COMMA); |
1195 | 0 | NCJunparseR(json->list.contents[i],buf,flags); |
1196 | 0 | } |
1197 | 0 | } |
1198 | 0 | bytesappendc(buf,NCJ_RBRACKET); |
1199 | 0 | break; |
1200 | 0 | case NCJ_NULL: |
1201 | 0 | bytesappend(buf,"null"); |
1202 | 0 | break; |
1203 | 0 | default: |
1204 | 0 | stat = NCJTHROW(NCJ_ERR); goto done; |
1205 | 0 | } |
1206 | 0 | done: |
1207 | 0 | return NCJTHROW(stat); |
1208 | 0 | } |
1209 | | |
1210 | | /* Escape a string and append to buf */ |
1211 | | static int |
1212 | | escape(const char* text, NCJbuf* buf) |
1213 | 0 | { |
1214 | 0 | const char* p = text; |
1215 | 0 | char c; |
1216 | 0 | for(;(c=*p++);) { |
1217 | 0 | char replace = 0; |
1218 | 0 | switch (c) { |
1219 | 0 | case '\b': replace = 'b'; break; |
1220 | 0 | case '\f': replace = 'f'; break; |
1221 | 0 | case '\n': replace = 'n'; break; |
1222 | 0 | case '\r': replace = 'r'; break; |
1223 | 0 | case '\t': replace = 't'; break; |
1224 | 0 | case NCJ_QUOTE: replace = '\"'; break; |
1225 | 0 | case NCJ_ESCAPE: replace = '\\'; break; |
1226 | 0 | default: break; |
1227 | 0 | } |
1228 | 0 | if(replace) { |
1229 | 0 | bytesappendc(buf,NCJ_ESCAPE); |
1230 | 0 | bytesappendc(buf,replace); |
1231 | 0 | } else |
1232 | 0 | bytesappendc(buf,(char)c); |
1233 | 0 | } |
1234 | 0 | return NCJTHROW(NCJ_OK); |
1235 | 0 | } |
1236 | | |
1237 | | static int |
1238 | | bytesappendquoted(NCJbuf* buf, const char* s) |
1239 | 0 | { |
1240 | 0 | bytesappend(buf,"\""); |
1241 | 0 | escape(s,buf); |
1242 | 0 | bytesappend(buf,"\""); |
1243 | 0 | return NCJTHROW(NCJ_OK); |
1244 | 0 | } |
1245 | | |
1246 | | static int |
1247 | | bytesappend(NCJbuf* buf, const char* s) |
1248 | 0 | { |
1249 | 0 | int stat = NCJ_OK; |
1250 | 0 | char* newtext = NULL; |
1251 | 0 | if(buf == NULL) |
1252 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
1253 | 0 | if(s == NULL) s = ""; |
1254 | 0 | if(buf->len == 0) { |
1255 | 0 | assert(buf->text == NULL); |
1256 | 0 | buf->text = strdup(s); |
1257 | 0 | if(buf->text == NULL) |
1258 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
1259 | 0 | buf->len = strlen(s); |
1260 | 0 | } else { |
1261 | 0 | size_t slen = strlen(s); |
1262 | 0 | size_t newlen = buf->len + slen + 1; |
1263 | 0 | if((newtext = (char*)malloc(newlen))==NULL) |
1264 | 0 | {stat = NCJTHROW(NCJ_ERR); goto done;} |
1265 | 0 | strcpy(newtext,buf->text); |
1266 | 0 | strcat(newtext,s); |
1267 | 0 | free(buf->text); buf->text = NULL; |
1268 | 0 | buf->text = newtext; newtext = NULL; |
1269 | 0 | buf->len = newlen; |
1270 | 0 | } |
1271 | | |
1272 | 0 | done: |
1273 | 0 | nullfree(newtext); |
1274 | 0 | return NCJTHROW(stat); |
1275 | 0 | } |
1276 | | |
1277 | | static int |
1278 | | bytesappendc(NCJbuf* bufp, const char c) |
1279 | 0 | { |
1280 | 0 | char s[2]; |
1281 | 0 | s[0] = c; |
1282 | 0 | s[1] = '\0'; |
1283 | 0 | return bytesappend(bufp,s); |
1284 | 0 | } |
1285 | | |
1286 | | OPTSTATIC const char* |
1287 | | NCJtotext(const NCjson* json, unsigned flags) |
1288 | 0 | { |
1289 | 0 | static char outtext[4096]; |
1290 | 0 | char* text = NULL; |
1291 | 0 | NC_UNUSED(flags); |
1292 | 0 | if(json == NULL) {strcpy(outtext,"<null>"); goto done;} |
1293 | 0 | (void)NCJunparse(json,0,&text); |
1294 | 0 | strncpy(outtext,text,sizeof(outtext)); |
1295 | 0 | nullfree(text); |
1296 | 0 | done: |
1297 | 0 | return outtext; |
1298 | 0 | } |
1299 | | |
1300 | | OPTSTATIC void |
1301 | | NCJdump(const NCjson* json, unsigned flags, FILE* out) |
1302 | 0 | { |
1303 | 0 | const char* text = NCJtotext(json,flags); |
1304 | 0 | if(out == NULL) out = stderr; |
1305 | 0 | fprintf(out,"%s\n",text); |
1306 | 0 | fflush(out); |
1307 | 0 | } |
1308 | | |
1309 | | static int |
1310 | | pairsort(const void* a, const void* b) |
1311 | 0 | { |
1312 | 0 | const NCjson** j1 = (const NCjson**)a; |
1313 | 0 | const NCjson** j2 = (const NCjson**)b; |
1314 | 0 | return strcmp(NCJstring(*j1),NCJstring(*j2)); |
1315 | 0 | } |
1316 | | |
1317 | | OPTSTATIC void |
1318 | | NCJdictsort(NCjson* jdict) |
1319 | 0 | { |
1320 | 0 | assert(NCJsort(jdict) == NCJ_DICT); |
1321 | 0 | qsort((void*)NCJcontents(jdict),NCJdictlength(jdict),2*sizeof(NCjson*),pairsort); |
1322 | 0 | } |
1323 | | |
1324 | | /* Hack to avoid static unused warning */ |
1325 | | static void |
1326 | | netcdf_supresswarnings(void) |
1327 | 0 | { |
1328 | 0 | void* ignore = NULL; |
1329 | 0 | ignore = (void*)netcdf_supresswarnings; |
1330 | 0 | ignore = (void*)NCJparse; |
1331 | 0 | ignore = (void*)NCJparsen; |
1332 | 0 | ignore = (void*)NCJreclaim; |
1333 | 0 | ignore = (void*)NCJnew; |
1334 | 0 | ignore = (void*)NCJnewstring; |
1335 | 0 | ignore = (void*)NCJnewstringn; |
1336 | 0 | ignore = (void*)NCJdictget; |
1337 | 0 | ignore = (void*)NCJdictlookup; |
1338 | 0 | ignore = (void*)NCJcvt; |
1339 | 0 | ignore = (void*)NCJaddstring; |
1340 | 0 | ignore = (void*)NCJappend; |
1341 | 0 | ignore = (void*)NCJappendstring; |
1342 | 0 | ignore = (void*)NCJappendint; |
1343 | 0 | ignore = (void*)NCJinsert; |
1344 | 0 | ignore = (void*)NCJinsertstring; |
1345 | 0 | ignore = (void*)NCJinsertint; |
1346 | 0 | ignore = (void*)NCJoverwrite; |
1347 | 0 | ignore = (void*)NCJdictsort; |
1348 | 0 | ignore = (void*)NCJdump; |
1349 | 0 | (void)ignore; |
1350 | 0 | } |