2014-02-05 23:16:23 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2014-02-05 23:33:43 +00:00
|
|
|
#include <ctype.h>
|
2014-02-06 02:23:49 +00:00
|
|
|
#include <string.h>
|
2014-02-06 01:21:19 +00:00
|
|
|
#include <stdarg.h>
|
2014-02-06 05:27:48 +00:00
|
|
|
#include "jsonpull.h"
|
2014-02-05 23:33:43 +00:00
|
|
|
|
2014-02-06 07:35:35 +00:00
|
|
|
#define SIZE_FOR(i) (((i) + 31) & ~31)
|
|
|
|
|
2014-02-06 20:13:23 +00:00
|
|
|
static json_object *add_object(json_type type, json_object *parent, char **error) {
|
2014-02-05 23:33:43 +00:00
|
|
|
json_object *o = malloc(sizeof(struct json_object));
|
|
|
|
o->type = type;
|
|
|
|
o->parent = parent;
|
2014-02-06 00:10:22 +00:00
|
|
|
o->array = NULL;
|
2014-02-06 07:18:27 +00:00
|
|
|
o->keys = NULL;
|
|
|
|
o->values = NULL;
|
|
|
|
o->length = 0;
|
2014-02-06 00:10:22 +00:00
|
|
|
|
|
|
|
if (parent != NULL) {
|
|
|
|
if (parent->type == JSON_ARRAY) {
|
2014-02-06 07:35:35 +00:00
|
|
|
if (SIZE_FOR(parent->length + 1) != SIZE_FOR(parent->length)) {
|
|
|
|
parent->array = realloc(parent->array, SIZE_FOR(parent->length + 1) * sizeof(json_object *));
|
2014-02-06 07:18:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
parent->array[parent->length++] = o;
|
2014-02-06 20:51:32 +00:00
|
|
|
} else if (parent->type == JSON_HASH) {
|
2014-02-06 07:18:27 +00:00
|
|
|
if (parent->length > 0 && parent->values[parent->length - 1] == NULL) {
|
2014-02-06 05:34:17 +00:00
|
|
|
// Hash has key but no value, so this is the value
|
2014-02-06 20:13:23 +00:00
|
|
|
|
2014-02-06 07:18:27 +00:00
|
|
|
parent->values[parent->length - 1] = o;
|
2014-02-06 00:10:22 +00:00
|
|
|
} else {
|
2014-02-06 05:34:17 +00:00
|
|
|
// No current hash, so this is a key
|
2014-02-06 06:13:03 +00:00
|
|
|
|
|
|
|
if (type != JSON_STRING) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Hash key is not a string";
|
2014-02-06 20:51:32 +00:00
|
|
|
free(o);
|
|
|
|
return NULL;
|
2014-02-06 06:13:03 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 07:35:35 +00:00
|
|
|
if (SIZE_FOR(parent->length + 1) != SIZE_FOR(parent->length)) {
|
|
|
|
parent->keys = realloc(parent->keys, SIZE_FOR(parent->length + 1) * sizeof(json_object *));
|
|
|
|
parent->values = realloc(parent->values, SIZE_FOR(parent->length + 1) * sizeof(json_object *));
|
2014-02-06 07:18:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
parent->keys[parent->length] = o;
|
|
|
|
parent->values[parent->length] = NULL;
|
|
|
|
parent->length++;
|
2014-02-06 00:10:22 +00:00
|
|
|
}
|
2014-02-06 20:51:32 +00:00
|
|
|
} else {
|
|
|
|
*error = "Parent is not a container";
|
|
|
|
free(o);
|
|
|
|
return NULL;
|
2014-02-06 00:10:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-05 23:33:43 +00:00
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
2014-02-06 02:23:49 +00:00
|
|
|
json_object *json_hash_get(json_object *o, char *s) {
|
|
|
|
if (o == NULL || o->type != JSON_HASH) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-02-06 07:18:27 +00:00
|
|
|
int i;
|
|
|
|
for (i = 0; i < o->length; i++) {
|
|
|
|
if (o->keys[i] != NULL && o->keys[i]->type == JSON_STRING) {
|
|
|
|
if (strcmp(o->keys[i]->string, s) == 0) {
|
|
|
|
return o->values[i];
|
2014-02-06 02:23:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-02-06 05:27:48 +00:00
|
|
|
static int peek(FILE *f) {
|
2014-02-06 00:23:01 +00:00
|
|
|
int c = getc(f);
|
|
|
|
ungetc(c, f);
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
2014-02-06 00:39:50 +00:00
|
|
|
struct string {
|
|
|
|
char *buf;
|
|
|
|
int n;
|
|
|
|
int nalloc;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void string_init(struct string *s) {
|
|
|
|
s->nalloc = 500;
|
|
|
|
s->buf = malloc(s->nalloc);
|
|
|
|
s->n = 0;
|
|
|
|
s->buf[0] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
static void string_append(struct string *s, char c) {
|
|
|
|
if (s->n + 2 >= s->nalloc) {
|
|
|
|
s->nalloc += 500;
|
|
|
|
s->buf = realloc(s->buf, s->nalloc);
|
|
|
|
}
|
|
|
|
|
|
|
|
s->buf[s->n++] = c;
|
|
|
|
s->buf[s->n] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
static void string_free(struct string *s) {
|
|
|
|
free(s->buf);
|
|
|
|
}
|
|
|
|
|
2014-02-06 20:13:23 +00:00
|
|
|
json_object *json_parse(FILE *f, json_object *current, char **error) {
|
2014-02-06 20:19:33 +00:00
|
|
|
int current_is = 0;
|
2014-02-06 08:25:32 +00:00
|
|
|
int c;
|
|
|
|
again:
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// Whitespace
|
|
|
|
|
2014-02-06 20:31:12 +00:00
|
|
|
do {
|
2014-02-05 23:33:43 +00:00
|
|
|
c = getc(f);
|
|
|
|
if (c == EOF) {
|
2014-02-06 20:31:12 +00:00
|
|
|
if (current != NULL) {
|
2014-02-06 21:20:56 +00:00
|
|
|
*error = "Reached EOF without all containers being closed";
|
2014-02-06 20:31:12 +00:00
|
|
|
}
|
2014-02-06 21:20:56 +00:00
|
|
|
|
|
|
|
return NULL;
|
2014-02-05 23:33:43 +00:00
|
|
|
}
|
2014-02-06 20:31:12 +00:00
|
|
|
} while (c == ' ' || c == '\t' || c == '\r' || c == '\n');
|
2014-02-05 23:33:43 +00:00
|
|
|
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// Arrays
|
|
|
|
|
2014-02-05 23:33:43 +00:00
|
|
|
if (c == '[') {
|
2014-02-06 20:13:23 +00:00
|
|
|
current = add_object(JSON_ARRAY, current, error);
|
2014-02-06 20:51:32 +00:00
|
|
|
if (current == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2014-02-06 20:19:33 +00:00
|
|
|
current_is = '[';
|
2014-02-06 08:25:32 +00:00
|
|
|
goto again;
|
2014-02-05 23:33:43 +00:00
|
|
|
} else if (c == ']') {
|
2014-02-06 06:13:03 +00:00
|
|
|
if (current == NULL) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found ] at top level";
|
|
|
|
return NULL;
|
2014-02-06 06:13:03 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 20:19:33 +00:00
|
|
|
if (current_is == '[') { // Empty array
|
2014-02-06 08:25:32 +00:00
|
|
|
return current;
|
2014-02-06 20:19:33 +00:00
|
|
|
} else if (current_is) {
|
|
|
|
*error = "Found ] without final element";
|
|
|
|
return NULL;
|
2014-02-06 06:13:03 +00:00
|
|
|
}
|
|
|
|
|
2014-02-05 23:33:43 +00:00
|
|
|
if (current->parent == NULL || current->parent->type != JSON_ARRAY) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found ] without matching [";
|
|
|
|
return NULL;
|
2014-02-05 23:33:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return current->parent;
|
|
|
|
}
|
|
|
|
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// Hashes
|
|
|
|
|
2014-02-05 23:33:43 +00:00
|
|
|
if (c == '{') {
|
2014-02-06 20:13:23 +00:00
|
|
|
current = add_object(JSON_HASH, current, error);
|
2014-02-06 20:51:32 +00:00
|
|
|
if (current == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2014-02-06 20:19:33 +00:00
|
|
|
current_is = '{';
|
2014-02-06 08:25:32 +00:00
|
|
|
goto again;
|
2014-02-05 23:33:43 +00:00
|
|
|
} else if (c == '}') {
|
2014-02-06 06:13:03 +00:00
|
|
|
if (current == NULL) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found } at top level";
|
|
|
|
return NULL;
|
2014-02-06 06:13:03 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 20:19:33 +00:00
|
|
|
if (current_is == '{') { // Empty hash
|
2014-02-06 08:25:32 +00:00
|
|
|
return current;
|
2014-02-06 20:19:33 +00:00
|
|
|
} else if (current_is) {
|
|
|
|
*error = "Found } without final element";
|
|
|
|
return NULL;
|
2014-02-06 06:13:03 +00:00
|
|
|
}
|
|
|
|
|
2014-02-05 23:33:43 +00:00
|
|
|
if (current->parent == NULL || current->parent->type != JSON_HASH) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found } without matching {";
|
|
|
|
return NULL;
|
2014-02-05 23:33:43 +00:00
|
|
|
}
|
2014-02-06 07:18:27 +00:00
|
|
|
if (current->parent->length != 0 && current->parent->values[current->parent->length - 1] == NULL) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found hash key without value";
|
|
|
|
return NULL;
|
2014-02-06 02:34:02 +00:00
|
|
|
}
|
2014-02-05 23:33:43 +00:00
|
|
|
|
|
|
|
return current->parent;
|
|
|
|
}
|
2014-02-05 23:42:15 +00:00
|
|
|
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// Null
|
|
|
|
|
2014-02-05 23:42:15 +00:00
|
|
|
if (c == 'n') {
|
|
|
|
if (getc(f) != 'u' || getc(f) != 'l' || getc(f) != 'l') {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found misspelling of null";
|
|
|
|
return NULL;
|
2014-02-05 23:42:15 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 20:13:23 +00:00
|
|
|
return add_object(JSON_NULL, current, error);
|
2014-02-05 23:42:15 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// True
|
|
|
|
|
2014-02-05 23:42:15 +00:00
|
|
|
if (c == 't') {
|
|
|
|
if (getc(f) != 'r' || getc(f) != 'u' || getc(f) != 'e') {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found misspelling of true";
|
|
|
|
return NULL;
|
2014-02-05 23:42:15 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 20:13:23 +00:00
|
|
|
return add_object(JSON_TRUE, current, error);
|
2014-02-05 23:42:15 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// False
|
|
|
|
|
2014-02-05 23:42:15 +00:00
|
|
|
if (c == 'f') {
|
|
|
|
if (getc(f) != 'a' || getc(f) != 'l' || getc(f) != 's' || getc(f) != 'e') {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found misspelling of false";
|
|
|
|
return NULL;
|
2014-02-05 23:42:15 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 20:13:23 +00:00
|
|
|
return add_object(JSON_FALSE, current, error);
|
2014-02-05 23:42:15 +00:00
|
|
|
}
|
2014-02-06 00:00:01 +00:00
|
|
|
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// Comma
|
|
|
|
|
2014-02-06 00:00:01 +00:00
|
|
|
if (c == ',') {
|
2014-02-06 05:52:39 +00:00
|
|
|
if (current == NULL) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found comma at top level";
|
|
|
|
return NULL;
|
2014-02-06 05:52:39 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 20:13:23 +00:00
|
|
|
if (current->parent == NULL || (current->parent->type != JSON_ARRAY && current->parent->type != JSON_HASH)) {
|
|
|
|
*error = "Found comma outside of array or hash";
|
|
|
|
return NULL;
|
2014-02-06 00:00:01 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 20:25:59 +00:00
|
|
|
if (current->parent->type == JSON_HASH) {
|
|
|
|
if (current->parent->length == 0 || current->parent->values[current->parent->length - 1] == NULL) {
|
|
|
|
*error = "Found comma in hash without a hash value";
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-06 08:25:32 +00:00
|
|
|
current = current->parent;
|
2014-02-06 20:19:33 +00:00
|
|
|
current_is = ',';
|
2014-02-06 08:25:32 +00:00
|
|
|
goto again;
|
2014-02-06 00:00:01 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// Colon
|
|
|
|
|
2014-02-06 00:00:01 +00:00
|
|
|
if (c == ':') {
|
2014-02-06 05:52:39 +00:00
|
|
|
if (current == NULL) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found colon at top level";
|
|
|
|
return NULL;
|
2014-02-06 05:52:39 +00:00
|
|
|
}
|
|
|
|
|
2014-02-06 00:00:01 +00:00
|
|
|
if (current->parent == NULL || current->parent->type != JSON_HASH) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found colon outside of hash";
|
|
|
|
return NULL;
|
2014-02-06 00:00:01 +00:00
|
|
|
}
|
2014-02-06 07:18:27 +00:00
|
|
|
if (current->parent->length == 0 || current->parent->keys[current->parent->length - 1] == NULL) {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found colon without a hash key";
|
|
|
|
return NULL;
|
2014-02-06 00:10:22 +00:00
|
|
|
}
|
2014-02-06 00:00:01 +00:00
|
|
|
|
2014-02-06 08:25:32 +00:00
|
|
|
current = current->parent;
|
2014-02-06 20:19:33 +00:00
|
|
|
current_is = ':';
|
2014-02-06 08:25:32 +00:00
|
|
|
goto again;
|
2014-02-06 00:00:01 +00:00
|
|
|
}
|
2014-02-06 00:23:01 +00:00
|
|
|
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// Numbers
|
|
|
|
|
2014-02-06 00:23:01 +00:00
|
|
|
if (c == '-' || (c >= '0' && c <= '9')) {
|
2014-02-06 00:39:50 +00:00
|
|
|
struct string val;
|
|
|
|
string_init(&val);
|
2014-02-06 00:23:01 +00:00
|
|
|
|
2014-02-06 01:38:02 +00:00
|
|
|
if (c == '-') {
|
|
|
|
string_append(&val, c);
|
|
|
|
c = getc(f);
|
|
|
|
}
|
2014-02-06 00:23:01 +00:00
|
|
|
|
2014-02-06 01:38:02 +00:00
|
|
|
if (c == '0') {
|
|
|
|
string_append(&val, c);
|
|
|
|
} else if (c >= '1' && c <= '9') {
|
|
|
|
string_append(&val, c);
|
2014-02-06 00:23:01 +00:00
|
|
|
c = peek(f);
|
|
|
|
|
|
|
|
while (c >= '0' && c <= '9') {
|
2014-02-06 00:39:50 +00:00
|
|
|
string_append(&val, getc(f));
|
2014-02-06 00:23:01 +00:00
|
|
|
c = peek(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (peek(f) == '.') {
|
2014-02-06 00:39:50 +00:00
|
|
|
string_append(&val, getc(f));
|
2014-02-06 00:23:01 +00:00
|
|
|
|
|
|
|
c = peek(f);
|
|
|
|
while (c >= '0' && c <= '9') {
|
2014-02-06 00:39:50 +00:00
|
|
|
string_append(&val, getc(f));
|
2014-02-06 00:23:01 +00:00
|
|
|
c = peek(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c = peek(f);
|
|
|
|
if (c == 'e' || c == 'E') {
|
2014-02-06 00:39:50 +00:00
|
|
|
string_append(&val, getc(f));
|
2014-02-06 00:23:01 +00:00
|
|
|
|
|
|
|
c = peek(f);
|
|
|
|
if (c == '+' || c == '-') {
|
2014-02-06 00:39:50 +00:00
|
|
|
string_append(&val, getc(f));
|
2014-02-06 00:23:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
c = peek(f);
|
2014-02-06 21:26:56 +00:00
|
|
|
if (c < '0' || c > '9') {
|
|
|
|
*error = "Exponent without digits";
|
|
|
|
return NULL;
|
|
|
|
}
|
2014-02-06 00:23:01 +00:00
|
|
|
while (c >= '0' && c <= '9') {
|
2014-02-06 00:39:50 +00:00
|
|
|
string_append(&val, getc(f));
|
2014-02-06 00:23:01 +00:00
|
|
|
c = peek(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-06 20:13:23 +00:00
|
|
|
json_object *n = add_object(JSON_NUMBER, current, error);
|
2014-02-06 20:51:32 +00:00
|
|
|
if (n != NULL) {
|
|
|
|
n->number = atof(val.buf);
|
|
|
|
string_free(&val);
|
|
|
|
}
|
2014-02-06 00:23:01 +00:00
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
2014-02-06 00:45:20 +00:00
|
|
|
|
2014-02-06 02:03:12 +00:00
|
|
|
/////////////////////////// Strings
|
|
|
|
|
2014-02-06 00:45:20 +00:00
|
|
|
if (c == '"') {
|
|
|
|
struct string val;
|
|
|
|
string_init(&val);
|
|
|
|
|
|
|
|
while ((c = getc(f)) != EOF) {
|
|
|
|
if (c == '"') {
|
|
|
|
break;
|
|
|
|
} else if (c == '\\') {
|
|
|
|
c = getc(f);
|
|
|
|
|
|
|
|
if (c == '"') {
|
|
|
|
string_append(&val, '"');
|
|
|
|
} else if (c == '\\') {
|
|
|
|
string_append(&val, '\\');
|
|
|
|
} else if (c == '/') {
|
|
|
|
string_append(&val, '/');
|
|
|
|
} else if (c == 'b') {
|
|
|
|
string_append(&val, '\b');
|
|
|
|
} else if (c == 'f') {
|
|
|
|
string_append(&val, '\f');
|
|
|
|
} else if (c == 'n') {
|
|
|
|
string_append(&val, '\n');
|
|
|
|
} else if (c == 'r') {
|
|
|
|
string_append(&val, '\r');
|
|
|
|
} else if (c == 't') {
|
|
|
|
string_append(&val, '\t');
|
|
|
|
} else if (c == 'u') {
|
2014-02-06 05:42:49 +00:00
|
|
|
char hex[5] = "aaaa";
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
hex[i] = getc(f);
|
|
|
|
}
|
|
|
|
unsigned long ch = strtoul(hex, NULL, 16);
|
|
|
|
if (ch <= 0x7F) {
|
|
|
|
string_append(&val, ch);
|
|
|
|
} else if (ch <= 0x7FF) {
|
|
|
|
string_append(&val, 0xC0 | (ch >> 6));
|
|
|
|
string_append(&val, 0x80 | (ch & 0x3F));
|
|
|
|
} else {
|
|
|
|
string_append(&val, 0xE0 | (ch >> 12));
|
|
|
|
string_append(&val, 0x80 | ((ch >> 6) & 0x3F));
|
|
|
|
string_append(&val, 0x80 | (ch & 0x3F));
|
|
|
|
}
|
2014-02-06 00:45:20 +00:00
|
|
|
} else {
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found backslash followed by unknown character";
|
|
|
|
return NULL;
|
2014-02-06 00:45:20 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
string_append(&val, c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-06 20:13:23 +00:00
|
|
|
json_object *s = add_object(JSON_STRING, current, error);
|
2014-02-06 20:51:32 +00:00
|
|
|
if (s != NULL) {
|
|
|
|
s->string = val.buf;
|
2014-02-06 21:28:35 +00:00
|
|
|
s->length = val.n;
|
2014-02-06 20:51:32 +00:00
|
|
|
}
|
2014-02-06 00:45:20 +00:00
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2014-02-06 20:13:23 +00:00
|
|
|
*error = "Found unexpected character";
|
2014-02-06 02:23:49 +00:00
|
|
|
return NULL;
|
2014-02-05 23:33:43 +00:00
|
|
|
}
|