| #ifndef TINYGLTF_JSON_C_H_ |
| #define TINYGLTF_JSON_C_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| typedef enum tg3json_type { |
| TG3JSON_NULL = 0, |
| TG3JSON_BOOL = 1, |
| TG3JSON_INT = 2, |
| TG3JSON_REAL = 3, |
| TG3JSON_STRING = 4, |
| TG3JSON_ARRAY = 5, |
| TG3JSON_OBJECT = 6 |
| } tg3json_type; |
| |
| typedef struct tg3json_value tg3json_value; |
| |
| typedef struct tg3json_string { |
| char *ptr; |
| size_t len; |
| } tg3json_string; |
| |
| typedef struct tg3json_array { |
| tg3json_value *items; |
| size_t count; |
| } tg3json_array; |
| |
| typedef struct tg3json_object_entry { |
| char *key; |
| size_t key_len; |
| tg3json_value *value; |
| } tg3json_object_entry; |
| |
| typedef struct tg3json_object { |
| tg3json_object_entry *items; |
| size_t count; |
| } tg3json_object; |
| |
| struct tg3json_value { |
| tg3json_type type; |
| union { |
| int boolean; |
| int64_t integer; |
| double real; |
| tg3json_string string; |
| tg3json_array array; |
| tg3json_object object; |
| } u; |
| }; |
| |
| int tg3json_parse_n(const char *data, size_t len, size_t depth_limit, |
| tg3json_value *out_value, const char **out_error_pos); |
| int tg3json_parse(const char *begin, const char *end, size_t depth_limit, |
| tg3json_value *out_value, const char **out_error_pos); |
| void tg3json_value_free(tg3json_value *value); |
| void tg3json_value_init_null(tg3json_value *value); |
| void tg3json_value_init_bool(tg3json_value *value, int boolean_value); |
| void tg3json_value_init_int(tg3json_value *value, int64_t integer_value); |
| void tg3json_value_init_real(tg3json_value *value, double real_value); |
| int tg3json_value_init_string_n(tg3json_value *value, const char *str, size_t len); |
| int tg3json_value_init_string(tg3json_value *value, const char *str); |
| void tg3json_value_init_array(tg3json_value *value); |
| void tg3json_value_init_object(tg3json_value *value); |
| int tg3json_value_copy(tg3json_value *dst, const tg3json_value *src); |
| |
| const tg3json_value *tg3json_object_get(const tg3json_value *object, |
| const char *key); |
| const tg3json_value *tg3json_object_get_n(const tg3json_value *object, |
| const char *key, size_t key_len); |
| tg3json_value *tg3json_object_get_mut(tg3json_value *object, const char *key); |
| tg3json_value *tg3json_object_get_mut_n(tg3json_value *object, |
| const char *key, size_t key_len); |
| const tg3json_object_entry *tg3json_object_at(const tg3json_value *object, |
| size_t index); |
| size_t tg3json_object_size(const tg3json_value *object); |
| int tg3json_object_set_take_n(tg3json_value *object, const char *key, size_t key_len, |
| tg3json_value *value); |
| int tg3json_object_set_take(tg3json_value *object, const char *key, |
| tg3json_value *value); |
| int tg3json_object_set_copy_n(tg3json_value *object, const char *key, size_t key_len, |
| const tg3json_value *value); |
| int tg3json_object_set_copy(tg3json_value *object, const char *key, |
| const tg3json_value *value); |
| const tg3json_value *tg3json_array_get(const tg3json_value *array, size_t index); |
| size_t tg3json_array_size(const tg3json_value *array); |
| int tg3json_array_append_take(tg3json_value *array, tg3json_value *value); |
| int tg3json_array_append_copy(tg3json_value *array, const tg3json_value *value); |
| char *tg3json_stringify(const tg3json_value *value, size_t *out_len); |
| char *tg3json_stringify_pretty(const tg3json_value *value, int indent, size_t *out_len); |
| |
| #ifdef __cplusplus |
| } /* extern "C" */ |
| #endif |
| |
| #ifdef TINYGLTF_JSON_C_IMPLEMENTATION |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| typedef struct tg3json__parser { |
| const char *cur; |
| const char *end; |
| const char *error; |
| size_t depth_limit; |
| } tg3json__parser; |
| |
| typedef struct tg3json__buffer { |
| char *data; |
| size_t len; |
| size_t cap; |
| } tg3json__buffer; |
| |
| static void tg3json__init_value(tg3json_value *value) { |
| if (!value) return; |
| memset(value, 0, sizeof(*value)); |
| value->type = TG3JSON_NULL; |
| } |
| |
| static char *tg3json__strndup_local(const char *src, size_t len) { |
| char *dst = (char *)malloc(len + 1); |
| if (!dst) return NULL; |
| if (len > 0) memcpy(dst, src, len); |
| dst[len] = '\0'; |
| return dst; |
| } |
| |
| static int tg3json__reserve_bytes(void **ptr, size_t elem_size, |
| size_t needed, size_t *capacity) { |
| void *new_ptr; |
| size_t new_cap; |
| |
| if (needed <= *capacity) return 1; |
| new_cap = (*capacity > 0) ? *capacity : 8; |
| while (new_cap < needed) { |
| if (new_cap > ((size_t)-1) / 2) { |
| new_cap = needed; |
| break; |
| } |
| new_cap *= 2; |
| } |
| |
| if (elem_size != 0 && new_cap > ((size_t)-1) / elem_size) return 0; |
| new_ptr = realloc(*ptr, elem_size * new_cap); |
| if (!new_ptr) return 0; |
| *ptr = new_ptr; |
| *capacity = new_cap; |
| return 1; |
| } |
| |
| static const char *tg3json__skip_ws(const char *p, const char *end) { |
| while (p < end) { |
| unsigned char c = (unsigned char)*p; |
| if (c != ' ' && c != '\n' && c != '\r' && c != '\t') break; |
| ++p; |
| } |
| return p; |
| } |
| |
| static void tg3json__set_error(tg3json__parser *parser, const char *pos) { |
| if (!parser->error) parser->error = pos; |
| } |
| |
| static int tg3json__buf_append(tg3json__buffer *buf, const char *src, size_t len) { |
| if (len == 0) return 1; |
| if (!tg3json__reserve_bytes((void **)&buf->data, 1, buf->len + len + 1, &buf->cap)) { |
| return 0; |
| } |
| memcpy(buf->data + buf->len, src, len); |
| buf->len += len; |
| buf->data[buf->len] = '\0'; |
| return 1; |
| } |
| |
| static int tg3json__buf_putc(tg3json__buffer *buf, char c) { |
| if (!tg3json__reserve_bytes((void **)&buf->data, 1, buf->len + 2, &buf->cap)) { |
| return 0; |
| } |
| buf->data[buf->len++] = c; |
| buf->data[buf->len] = '\0'; |
| return 1; |
| } |
| |
| static int tg3json__hex4(const char *p, uint32_t *out) { |
| uint32_t value = 0; |
| size_t i; |
| for (i = 0; i < 4; ++i) { |
| unsigned char c = (unsigned char)p[i]; |
| value <<= 4; |
| if (c >= '0' && c <= '9') value |= (uint32_t)(c - '0'); |
| else if (c >= 'a' && c <= 'f') value |= (uint32_t)(10 + c - 'a'); |
| else if (c >= 'A' && c <= 'F') value |= (uint32_t)(10 + c - 'A'); |
| else return 0; |
| } |
| *out = value; |
| return 1; |
| } |
| |
| static int tg3json__append_utf8(tg3json__buffer *buf, uint32_t cp) { |
| char tmp[4]; |
| size_t len = 0; |
| if (cp <= 0x7Fu) { |
| tmp[len++] = (char)cp; |
| } else if (cp <= 0x7FFu) { |
| tmp[len++] = (char)(0xC0u | ((cp >> 6) & 0x1Fu)); |
| tmp[len++] = (char)(0x80u | (cp & 0x3Fu)); |
| } else if (cp <= 0xFFFFu) { |
| tmp[len++] = (char)(0xE0u | ((cp >> 12) & 0x0Fu)); |
| tmp[len++] = (char)(0x80u | ((cp >> 6) & 0x3Fu)); |
| tmp[len++] = (char)(0x80u | (cp & 0x3Fu)); |
| } else if (cp <= 0x10FFFFu) { |
| tmp[len++] = (char)(0xF0u | ((cp >> 18) & 0x07u)); |
| tmp[len++] = (char)(0x80u | ((cp >> 12) & 0x3Fu)); |
| tmp[len++] = (char)(0x80u | ((cp >> 6) & 0x3Fu)); |
| tmp[len++] = (char)(0x80u | (cp & 0x3Fu)); |
| } else { |
| return 0; |
| } |
| return tg3json__buf_append(buf, tmp, len); |
| } |
| |
| static int tg3json__parse_string_raw(tg3json__parser *parser, |
| char **out_str, size_t *out_len) { |
| tg3json__buffer buf; |
| const char *start; |
| memset(&buf, 0, sizeof(buf)); |
| |
| if (parser->cur >= parser->end || *parser->cur != '"') { |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| } |
| |
| ++parser->cur; |
| start = parser->cur; |
| while (parser->cur < parser->end) { |
| unsigned char c = (unsigned char)*parser->cur; |
| if (c == '"') { |
| if (!tg3json__buf_append(&buf, start, (size_t)(parser->cur - start))) goto oom; |
| ++parser->cur; |
| *out_str = buf.data; |
| *out_len = buf.len; |
| return 1; |
| } |
| if (c == '\\') { |
| uint32_t codepoint; |
| if (!tg3json__buf_append(&buf, start, (size_t)(parser->cur - start))) goto oom; |
| ++parser->cur; |
| if (parser->cur >= parser->end) break; |
| switch (*parser->cur) { |
| case '"': if (!tg3json__buf_putc(&buf, '"')) goto oom; break; |
| case '\\': if (!tg3json__buf_putc(&buf, '\\')) goto oom; break; |
| case '/': if (!tg3json__buf_putc(&buf, '/')) goto oom; break; |
| case 'b': if (!tg3json__buf_putc(&buf, '\b')) goto oom; break; |
| case 'f': if (!tg3json__buf_putc(&buf, '\f')) goto oom; break; |
| case 'n': if (!tg3json__buf_putc(&buf, '\n')) goto oom; break; |
| case 'r': if (!tg3json__buf_putc(&buf, '\r')) goto oom; break; |
| case 't': if (!tg3json__buf_putc(&buf, '\t')) goto oom; break; |
| case 'u': { |
| if ((size_t)(parser->end - parser->cur) < 5) break; |
| if (!tg3json__hex4(parser->cur + 1, &codepoint)) break; |
| parser->cur += 4; |
| if (codepoint >= 0xD800u && codepoint <= 0xDBFFu) { |
| uint32_t low; |
| if ((size_t)(parser->end - parser->cur) < 7 || parser->cur[1] != '\\' || parser->cur[2] != 'u') break; |
| if (!tg3json__hex4(parser->cur + 3, &low)) break; |
| if (low < 0xDC00u || low > 0xDFFFu) break; |
| codepoint = 0x10000u + (((codepoint - 0xD800u) << 10) | (low - 0xDC00u)); |
| parser->cur += 6; |
| } else if (codepoint >= 0xDC00u && codepoint <= 0xDFFFu) { |
| break; |
| } |
| if (!tg3json__append_utf8(&buf, codepoint)) goto oom; |
| break; |
| } |
| default: |
| tg3json__set_error(parser, parser->cur); |
| free(buf.data); |
| return 0; |
| } |
| ++parser->cur; |
| start = parser->cur; |
| continue; |
| } |
| if (c < 0x20u) break; |
| ++parser->cur; |
| } |
| |
| tg3json__set_error(parser, parser->cur); |
| free(buf.data); |
| return 0; |
| |
| oom: |
| tg3json__set_error(parser, parser->cur); |
| free(buf.data); |
| return 0; |
| } |
| |
| static int tg3json__parse_value(tg3json__parser *parser, size_t depth, |
| tg3json_value *out_value); |
| |
| static int tg3json__parse_array(tg3json__parser *parser, size_t depth, |
| tg3json_value *out_value) { |
| tg3json_value *items = NULL; |
| size_t count = 0; |
| size_t cap = 0; |
| |
| ++parser->cur; |
| parser->cur = tg3json__skip_ws(parser->cur, parser->end); |
| if (parser->cur < parser->end && *parser->cur == ']') { |
| ++parser->cur; |
| out_value->type = TG3JSON_ARRAY; |
| out_value->u.array.items = NULL; |
| out_value->u.array.count = 0; |
| return 1; |
| } |
| |
| while (parser->cur < parser->end) { |
| tg3json_value value; |
| tg3json__init_value(&value); |
| if (!tg3json__reserve_bytes((void **)&items, sizeof(*items), count + 1, &cap)) goto oom; |
| if (!tg3json__parse_value(parser, depth + 1, &value)) goto fail; |
| items[count++] = value; |
| parser->cur = tg3json__skip_ws(parser->cur, parser->end); |
| if (parser->cur >= parser->end) break; |
| if (*parser->cur == ',') { |
| ++parser->cur; |
| parser->cur = tg3json__skip_ws(parser->cur, parser->end); |
| continue; |
| } |
| if (*parser->cur == ']') { |
| ++parser->cur; |
| out_value->type = TG3JSON_ARRAY; |
| out_value->u.array.items = items; |
| out_value->u.array.count = count; |
| return 1; |
| } |
| break; |
| } |
| |
| fail: |
| while (count > 0) { |
| --count; |
| tg3json_value_free(&items[count]); |
| } |
| free(items); |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| |
| oom: |
| while (count > 0) { |
| --count; |
| tg3json_value_free(&items[count]); |
| } |
| free(items); |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| } |
| |
| static int tg3json__parse_object(tg3json__parser *parser, size_t depth, |
| tg3json_value *out_value) { |
| tg3json_object_entry *items = NULL; |
| size_t count = 0; |
| size_t cap = 0; |
| |
| ++parser->cur; |
| parser->cur = tg3json__skip_ws(parser->cur, parser->end); |
| if (parser->cur < parser->end && *parser->cur == '}') { |
| ++parser->cur; |
| out_value->type = TG3JSON_OBJECT; |
| out_value->u.object.items = NULL; |
| out_value->u.object.count = 0; |
| return 1; |
| } |
| |
| while (parser->cur < parser->end) { |
| char *key = NULL; |
| size_t key_len = 0; |
| tg3json_value value; |
| tg3json__init_value(&value); |
| if (!tg3json__parse_string_raw(parser, &key, &key_len)) goto fail; |
| parser->cur = tg3json__skip_ws(parser->cur, parser->end); |
| if (parser->cur >= parser->end || *parser->cur != ':') { |
| free(key); |
| goto fail; |
| } |
| ++parser->cur; |
| parser->cur = tg3json__skip_ws(parser->cur, parser->end); |
| if (!tg3json__parse_value(parser, depth + 1, &value)) { |
| free(key); |
| goto fail; |
| } |
| if (!tg3json__reserve_bytes((void **)&items, sizeof(*items), count + 1, &cap)) { |
| free(key); |
| tg3json_value_free(&value); |
| goto oom; |
| } |
| items[count].key = key; |
| items[count].key_len = key_len; |
| items[count].value = (tg3json_value *)malloc(sizeof(tg3json_value)); |
| if (!items[count].value) { |
| free(key); |
| tg3json_value_free(&value); |
| goto oom; |
| } |
| *items[count].value = value; |
| ++count; |
| parser->cur = tg3json__skip_ws(parser->cur, parser->end); |
| if (parser->cur >= parser->end) break; |
| if (*parser->cur == ',') { |
| ++parser->cur; |
| parser->cur = tg3json__skip_ws(parser->cur, parser->end); |
| continue; |
| } |
| if (*parser->cur == '}') { |
| ++parser->cur; |
| out_value->type = TG3JSON_OBJECT; |
| out_value->u.object.items = items; |
| out_value->u.object.count = count; |
| return 1; |
| } |
| break; |
| } |
| |
| fail: |
| while (count > 0) { |
| --count; |
| free(items[count].key); |
| if (items[count].value) { |
| tg3json_value_free(items[count].value); |
| free(items[count].value); |
| } |
| } |
| free(items); |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| |
| oom: |
| while (count > 0) { |
| --count; |
| free(items[count].key); |
| if (items[count].value) { |
| tg3json_value_free(items[count].value); |
| free(items[count].value); |
| } |
| } |
| free(items); |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| } |
| |
| static int tg3json__parse_number(tg3json__parser *parser, tg3json_value *out_value) { |
| const char *start = parser->cur; |
| const char *p = parser->cur; |
| int is_real = 0; |
| size_t len; |
| char stack_buf[128]; |
| char *num_buf = stack_buf; |
| |
| if (*p == '-') ++p; |
| if (p >= parser->end) goto fail; |
| if (*p == '0') { |
| ++p; |
| } else if (*p >= '1' && *p <= '9') { |
| do { ++p; } while (p < parser->end && *p >= '0' && *p <= '9'); |
| } else { |
| goto fail; |
| } |
| if (p < parser->end && *p == '.') { |
| is_real = 1; |
| ++p; |
| if (p >= parser->end || *p < '0' || *p > '9') goto fail; |
| do { ++p; } while (p < parser->end && *p >= '0' && *p <= '9'); |
| } |
| if (p < parser->end && (*p == 'e' || *p == 'E')) { |
| is_real = 1; |
| ++p; |
| if (p < parser->end && (*p == '+' || *p == '-')) ++p; |
| if (p >= parser->end || *p < '0' || *p > '9') goto fail; |
| do { ++p; } while (p < parser->end && *p >= '0' && *p <= '9'); |
| } |
| |
| len = (size_t)(p - start); |
| if (len + 1 > sizeof(stack_buf)) { |
| num_buf = (char *)malloc(len + 1); |
| if (!num_buf) goto oom; |
| } |
| memcpy(num_buf, start, len); |
| num_buf[len] = '\0'; |
| |
| if (!is_real) { |
| char *endptr = NULL; |
| long long v; |
| errno = 0; |
| v = strtoll(num_buf, &endptr, 10); |
| if (errno == 0 && endptr == num_buf + len) { |
| out_value->type = TG3JSON_INT; |
| out_value->u.integer = (int64_t)v; |
| parser->cur = p; |
| if (num_buf != stack_buf) free(num_buf); |
| return 1; |
| } |
| is_real = 1; |
| } |
| |
| { |
| char *endptr = NULL; |
| errno = 0; |
| out_value->u.real = strtod(num_buf, &endptr); |
| if (endptr != num_buf + len) { |
| if (num_buf != stack_buf) free(num_buf); |
| goto fail; |
| } |
| out_value->type = TG3JSON_REAL; |
| parser->cur = p; |
| if (num_buf != stack_buf) free(num_buf); |
| return 1; |
| } |
| |
| fail: |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| oom: |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| } |
| |
| static int tg3json__parse_value(tg3json__parser *parser, size_t depth, |
| tg3json_value *out_value) { |
| tg3json__init_value(out_value); |
| if (depth > parser->depth_limit) { |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| } |
| parser->cur = tg3json__skip_ws(parser->cur, parser->end); |
| if (parser->cur >= parser->end) { |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| } |
| switch (*parser->cur) { |
| case 'n': |
| if ((size_t)(parser->end - parser->cur) >= 4 && memcmp(parser->cur, "null", 4) == 0) { |
| out_value->type = TG3JSON_NULL; |
| parser->cur += 4; |
| return 1; |
| } |
| break; |
| case 't': |
| if ((size_t)(parser->end - parser->cur) >= 4 && memcmp(parser->cur, "true", 4) == 0) { |
| out_value->type = TG3JSON_BOOL; |
| out_value->u.boolean = 1; |
| parser->cur += 4; |
| return 1; |
| } |
| break; |
| case 'f': |
| if ((size_t)(parser->end - parser->cur) >= 5 && memcmp(parser->cur, "false", 5) == 0) { |
| out_value->type = TG3JSON_BOOL; |
| out_value->u.boolean = 0; |
| parser->cur += 5; |
| return 1; |
| } |
| break; |
| case '"': |
| out_value->type = TG3JSON_STRING; |
| return tg3json__parse_string_raw(parser, &out_value->u.string.ptr, |
| &out_value->u.string.len); |
| case '[': |
| return tg3json__parse_array(parser, depth, out_value); |
| case '{': |
| return tg3json__parse_object(parser, depth, out_value); |
| default: |
| if (*parser->cur == '-' || (*parser->cur >= '0' && *parser->cur <= '9')) { |
| return tg3json__parse_number(parser, out_value); |
| } |
| break; |
| } |
| tg3json__set_error(parser, parser->cur); |
| return 0; |
| } |
| |
| int tg3json_parse_n(const char *data, size_t len, size_t depth_limit, |
| tg3json_value *out_value, const char **out_error_pos) { |
| tg3json__parser parser; |
| tg3json__init_value(out_value); |
| parser.cur = data; |
| parser.end = data + len; |
| parser.error = NULL; |
| parser.depth_limit = depth_limit ? depth_limit : 512; |
| |
| if (!tg3json__parse_value(&parser, 0, out_value)) { |
| if (out_error_pos) *out_error_pos = parser.error; |
| tg3json_value_free(out_value); |
| return 0; |
| } |
| parser.cur = tg3json__skip_ws(parser.cur, parser.end); |
| if (parser.cur != parser.end) { |
| if (out_error_pos) *out_error_pos = parser.cur; |
| tg3json_value_free(out_value); |
| return 0; |
| } |
| if (out_error_pos) *out_error_pos = NULL; |
| return 1; |
| } |
| |
| int tg3json_parse(const char *begin, const char *end, size_t depth_limit, |
| tg3json_value *out_value, const char **out_error_pos) { |
| if (!begin || !end || end < begin) { |
| if (out_error_pos) *out_error_pos = begin; |
| return 0; |
| } |
| return tg3json_parse_n(begin, (size_t)(end - begin), depth_limit, |
| out_value, out_error_pos); |
| } |
| |
| void tg3json_value_free(tg3json_value *value) { |
| size_t i; |
| if (!value) return; |
| switch (value->type) { |
| case TG3JSON_STRING: |
| free(value->u.string.ptr); |
| break; |
| case TG3JSON_ARRAY: |
| for (i = 0; i < value->u.array.count; ++i) { |
| tg3json_value_free(&value->u.array.items[i]); |
| } |
| free(value->u.array.items); |
| break; |
| case TG3JSON_OBJECT: |
| for (i = 0; i < value->u.object.count; ++i) { |
| free(value->u.object.items[i].key); |
| if (value->u.object.items[i].value) { |
| tg3json_value_free(value->u.object.items[i].value); |
| free(value->u.object.items[i].value); |
| } |
| } |
| free(value->u.object.items); |
| break; |
| case TG3JSON_NULL: |
| case TG3JSON_BOOL: |
| case TG3JSON_INT: |
| case TG3JSON_REAL: |
| /* Scalar variants own no heap memory. */ |
| break; |
| } |
| tg3json__init_value(value); |
| } |
| |
| void tg3json_value_init_null(tg3json_value *value) { |
| tg3json__init_value(value); |
| } |
| |
| void tg3json_value_init_bool(tg3json_value *value, int boolean_value) { |
| tg3json__init_value(value); |
| value->type = TG3JSON_BOOL; |
| value->u.boolean = boolean_value ? 1 : 0; |
| } |
| |
| void tg3json_value_init_int(tg3json_value *value, int64_t integer_value) { |
| tg3json__init_value(value); |
| value->type = TG3JSON_INT; |
| value->u.integer = integer_value; |
| } |
| |
| void tg3json_value_init_real(tg3json_value *value, double real_value) { |
| tg3json__init_value(value); |
| value->type = TG3JSON_REAL; |
| value->u.real = real_value; |
| } |
| |
| int tg3json_value_init_string_n(tg3json_value *value, const char *str, size_t len) { |
| tg3json__init_value(value); |
| value->type = TG3JSON_STRING; |
| value->u.string.ptr = tg3json__strndup_local(str ? str : "", str ? len : 0); |
| if (!value->u.string.ptr) { |
| tg3json__init_value(value); |
| return 0; |
| } |
| value->u.string.len = str ? len : 0; |
| return 1; |
| } |
| |
| int tg3json_value_init_string(tg3json_value *value, const char *str) { |
| return tg3json_value_init_string_n(value, str, str ? strlen(str) : 0); |
| } |
| |
| void tg3json_value_init_array(tg3json_value *value) { |
| tg3json__init_value(value); |
| value->type = TG3JSON_ARRAY; |
| } |
| |
| void tg3json_value_init_object(tg3json_value *value) { |
| tg3json__init_value(value); |
| value->type = TG3JSON_OBJECT; |
| } |
| |
| int tg3json_value_copy(tg3json_value *dst, const tg3json_value *src) { |
| size_t i; |
| tg3json__init_value(dst); |
| if (!src) return 1; |
| switch (src->type) { |
| case TG3JSON_NULL: |
| return 1; |
| case TG3JSON_BOOL: |
| tg3json_value_init_bool(dst, src->u.boolean); |
| return 1; |
| case TG3JSON_INT: |
| tg3json_value_init_int(dst, src->u.integer); |
| return 1; |
| case TG3JSON_REAL: |
| tg3json_value_init_real(dst, src->u.real); |
| return 1; |
| case TG3JSON_STRING: |
| return tg3json_value_init_string_n(dst, src->u.string.ptr, src->u.string.len); |
| case TG3JSON_ARRAY: |
| tg3json_value_init_array(dst); |
| for (i = 0; i < src->u.array.count; ++i) { |
| if (!tg3json_array_append_copy(dst, &src->u.array.items[i])) { |
| tg3json_value_free(dst); |
| return 0; |
| } |
| } |
| return 1; |
| case TG3JSON_OBJECT: |
| tg3json_value_init_object(dst); |
| for (i = 0; i < src->u.object.count; ++i) { |
| if (!tg3json_object_set_copy_n(dst, src->u.object.items[i].key, |
| src->u.object.items[i].key_len, |
| src->u.object.items[i].value)) { |
| tg3json_value_free(dst); |
| return 0; |
| } |
| } |
| return 1; |
| } |
| return 0; /* unreachable: all enum cases handled above. */ |
| } |
| |
| const tg3json_value *tg3json_object_get_n(const tg3json_value *object, |
| const char *key, size_t key_len) { |
| size_t i; |
| if (!object || object->type != TG3JSON_OBJECT) return NULL; |
| for (i = 0; i < object->u.object.count; ++i) { |
| const tg3json_object_entry *entry = &object->u.object.items[i]; |
| if (entry->key_len == key_len && memcmp(entry->key, key, key_len) == 0) { |
| return entry->value; |
| } |
| } |
| return NULL; |
| } |
| |
| const tg3json_value *tg3json_object_get(const tg3json_value *object, |
| const char *key) { |
| if (!key) return NULL; |
| return tg3json_object_get_n(object, key, strlen(key)); |
| } |
| |
| tg3json_value *tg3json_object_get_mut_n(tg3json_value *object, |
| const char *key, size_t key_len) { |
| size_t i; |
| if (!object || object->type != TG3JSON_OBJECT) return NULL; |
| for (i = 0; i < object->u.object.count; ++i) { |
| tg3json_object_entry *entry = &object->u.object.items[i]; |
| if (entry->key_len == key_len && memcmp(entry->key, key, key_len) == 0) { |
| return entry->value; |
| } |
| } |
| return NULL; |
| } |
| |
| tg3json_value *tg3json_object_get_mut(tg3json_value *object, const char *key) { |
| if (!key) return NULL; |
| return tg3json_object_get_mut_n(object, key, strlen(key)); |
| } |
| |
| const tg3json_object_entry *tg3json_object_at(const tg3json_value *object, |
| size_t index) { |
| if (!object || object->type != TG3JSON_OBJECT || index >= object->u.object.count) { |
| return NULL; |
| } |
| return &object->u.object.items[index]; |
| } |
| |
| size_t tg3json_object_size(const tg3json_value *object) { |
| if (!object || object->type != TG3JSON_OBJECT) return 0; |
| return object->u.object.count; |
| } |
| |
| int tg3json_object_set_take_n(tg3json_value *object, const char *key, size_t key_len, |
| tg3json_value *value) { |
| tg3json_value *existing; |
| tg3json_object_entry *entry; |
| size_t cap; |
| if (!object || object->type != TG3JSON_OBJECT || !value || !key) return 0; |
| existing = tg3json_object_get_mut_n(object, key, key_len); |
| if (existing) { |
| tg3json_value_free(existing); |
| *existing = *value; |
| tg3json__init_value(value); |
| return 1; |
| } |
| cap = object->u.object.count; |
| if (!tg3json__reserve_bytes((void **)&object->u.object.items, sizeof(*object->u.object.items), |
| object->u.object.count + 1, &cap)) { |
| return 0; |
| } |
| entry = &object->u.object.items[object->u.object.count]; |
| entry->key = tg3json__strndup_local(key, key_len); |
| if (!entry->key) return 0; |
| entry->key_len = key_len; |
| entry->value = (tg3json_value *)malloc(sizeof(tg3json_value)); |
| if (!entry->value) { |
| free(entry->key); |
| entry->key = NULL; |
| entry->key_len = 0; |
| return 0; |
| } |
| *entry->value = *value; |
| tg3json__init_value(value); |
| object->u.object.count += 1; |
| return 1; |
| } |
| |
| int tg3json_object_set_take(tg3json_value *object, const char *key, |
| tg3json_value *value) { |
| if (!key) return 0; |
| return tg3json_object_set_take_n(object, key, strlen(key), value); |
| } |
| |
| int tg3json_object_set_copy_n(tg3json_value *object, const char *key, size_t key_len, |
| const tg3json_value *value) { |
| tg3json_value copy; |
| tg3json__init_value(©); |
| if (!tg3json_value_copy(©, value)) return 0; |
| if (!tg3json_object_set_take_n(object, key, key_len, ©)) { |
| tg3json_value_free(©); |
| return 0; |
| } |
| return 1; |
| } |
| |
| int tg3json_object_set_copy(tg3json_value *object, const char *key, |
| const tg3json_value *value) { |
| if (!key) return 0; |
| return tg3json_object_set_copy_n(object, key, strlen(key), value); |
| } |
| |
| const tg3json_value *tg3json_array_get(const tg3json_value *array, size_t index) { |
| if (!array || array->type != TG3JSON_ARRAY || index >= array->u.array.count) { |
| return NULL; |
| } |
| return &array->u.array.items[index]; |
| } |
| |
| size_t tg3json_array_size(const tg3json_value *array) { |
| if (!array || array->type != TG3JSON_ARRAY) return 0; |
| return array->u.array.count; |
| } |
| |
| int tg3json_array_append_take(tg3json_value *array, tg3json_value *value) { |
| size_t cap; |
| if (!array || array->type != TG3JSON_ARRAY || !value) return 0; |
| cap = array->u.array.count; |
| if (!tg3json__reserve_bytes((void **)&array->u.array.items, sizeof(*array->u.array.items), |
| array->u.array.count + 1, &cap)) { |
| return 0; |
| } |
| array->u.array.items[array->u.array.count++] = *value; |
| tg3json__init_value(value); |
| return 1; |
| } |
| |
| int tg3json_array_append_copy(tg3json_value *array, const tg3json_value *value) { |
| tg3json_value copy; |
| tg3json__init_value(©); |
| if (!tg3json_value_copy(©, value)) return 0; |
| if (!tg3json_array_append_take(array, ©)) { |
| tg3json_value_free(©); |
| return 0; |
| } |
| return 1; |
| } |
| |
| static int tg3json__indent(tg3json__buffer *buf, int indent, int depth) { |
| int i; |
| if (indent <= 0) return 1; |
| if (!tg3json__buf_putc(buf, '\n')) return 0; |
| for (i = 0; i < indent * depth; ++i) { |
| if (!tg3json__buf_putc(buf, ' ')) return 0; |
| } |
| return 1; |
| } |
| |
| static int tg3json__stringify_value_ex(tg3json__buffer *buf, const tg3json_value *value, |
| int indent, int depth) { |
| size_t i; |
| char numbuf[64]; |
| switch (value->type) { |
| case TG3JSON_NULL: |
| return tg3json__buf_append(buf, "null", 4); |
| case TG3JSON_BOOL: |
| return value->u.boolean ? tg3json__buf_append(buf, "true", 4) |
| : tg3json__buf_append(buf, "false", 5); |
| case TG3JSON_INT: |
| snprintf(numbuf, sizeof(numbuf), "%lld", (long long)value->u.integer); |
| return tg3json__buf_append(buf, numbuf, strlen(numbuf)); |
| case TG3JSON_REAL: |
| snprintf(numbuf, sizeof(numbuf), "%.17g", value->u.real); |
| { |
| const char *b = numbuf; |
| if (*b == '-') ++b; |
| if (*b == 'n' || *b == 'N' || *b == 'i' || *b == 'I') { |
| return tg3json__buf_append(buf, "null", 4); |
| } |
| } |
| if (!strchr(numbuf, '.') && !strchr(numbuf, 'e') && !strchr(numbuf, 'E')) { |
| size_t nlen = strlen(numbuf); |
| if (nlen + 2 < sizeof(numbuf)) { |
| numbuf[nlen] = '.'; |
| numbuf[nlen + 1] = '0'; |
| numbuf[nlen + 2] = '\0'; |
| } |
| } |
| return tg3json__buf_append(buf, numbuf, strlen(numbuf)); |
| case TG3JSON_STRING: |
| if (!tg3json__buf_putc(buf, '"')) return 0; |
| for (i = 0; i < value->u.string.len; ++i) { |
| unsigned char c = (unsigned char)value->u.string.ptr[i]; |
| switch (c) { |
| case '"': if (!tg3json__buf_append(buf, "\\\"", 2)) return 0; break; |
| case '\\': if (!tg3json__buf_append(buf, "\\\\", 2)) return 0; break; |
| case '\b': if (!tg3json__buf_append(buf, "\\b", 2)) return 0; break; |
| case '\f': if (!tg3json__buf_append(buf, "\\f", 2)) return 0; break; |
| case '\n': if (!tg3json__buf_append(buf, "\\n", 2)) return 0; break; |
| case '\r': if (!tg3json__buf_append(buf, "\\r", 2)) return 0; break; |
| case '\t': if (!tg3json__buf_append(buf, "\\t", 2)) return 0; break; |
| default: |
| if (c < 0x20u) { |
| snprintf(numbuf, sizeof(numbuf), "\\u%04x", (unsigned int)c); |
| if (!tg3json__buf_append(buf, numbuf, 6)) return 0; |
| } else { |
| if (!tg3json__buf_putc(buf, (char)c)) return 0; |
| } |
| break; |
| } |
| } |
| return tg3json__buf_putc(buf, '"'); |
| case TG3JSON_ARRAY: |
| if (!tg3json__buf_putc(buf, '[')) return 0; |
| for (i = 0; i < value->u.array.count; ++i) { |
| if (i > 0 && !tg3json__buf_putc(buf, ',')) return 0; |
| if (indent > 0 && !tg3json__indent(buf, indent, depth + 1)) return 0; |
| if (!tg3json__stringify_value_ex(buf, &value->u.array.items[i], indent, depth + 1)) return 0; |
| } |
| if (indent > 0 && value->u.array.count > 0 && !tg3json__indent(buf, indent, depth)) return 0; |
| return tg3json__buf_putc(buf, ']'); |
| case TG3JSON_OBJECT: |
| if (!tg3json__buf_putc(buf, '{')) return 0; |
| for (i = 0; i < value->u.object.count; ++i) { |
| tg3json_value key_value; |
| if (i > 0 && !tg3json__buf_putc(buf, ',')) return 0; |
| if (indent > 0 && !tg3json__indent(buf, indent, depth + 1)) return 0; |
| tg3json__init_value(&key_value); |
| key_value.type = TG3JSON_STRING; |
| key_value.u.string.ptr = value->u.object.items[i].key; |
| key_value.u.string.len = value->u.object.items[i].key_len; |
| if (!tg3json__stringify_value_ex(buf, &key_value, indent, depth + 1)) return 0; |
| if (!tg3json__buf_putc(buf, ':')) return 0; |
| if (indent > 0 && !tg3json__buf_putc(buf, ' ')) return 0; |
| if (!tg3json__stringify_value_ex(buf, value->u.object.items[i].value, indent, depth + 1)) return 0; |
| } |
| if (indent > 0 && value->u.object.count > 0 && !tg3json__indent(buf, indent, depth)) return 0; |
| return tg3json__buf_putc(buf, '}'); |
| } |
| return 0; /* unreachable: all enum cases handled above. */ |
| } |
| |
| char *tg3json_stringify(const tg3json_value *value, size_t *out_len) { |
| tg3json__buffer buf; |
| memset(&buf, 0, sizeof(buf)); |
| if (!value || !tg3json__stringify_value_ex(&buf, value, -1, 0)) { |
| free(buf.data); |
| if (out_len) *out_len = 0; |
| return NULL; |
| } |
| if (!buf.data) { |
| buf.data = (char *)malloc(1); |
| if (!buf.data) { |
| if (out_len) *out_len = 0; |
| return NULL; |
| } |
| buf.data[0] = '\0'; |
| } |
| if (out_len) *out_len = buf.len; |
| return buf.data; |
| } |
| |
| char *tg3json_stringify_pretty(const tg3json_value *value, int indent, size_t *out_len) { |
| tg3json__buffer buf; |
| memset(&buf, 0, sizeof(buf)); |
| if (!value || !tg3json__stringify_value_ex(&buf, value, indent, 0)) { |
| free(buf.data); |
| if (out_len) *out_len = 0; |
| return NULL; |
| } |
| if (!buf.data) { |
| buf.data = (char *)malloc(1); |
| if (!buf.data) { |
| if (out_len) *out_len = 0; |
| return NULL; |
| } |
| buf.data[0] = '\0'; |
| } |
| if (out_len) *out_len = buf.len; |
| return buf.data; |
| } |
| |
| #endif /* TINYGLTF_JSON_C_IMPLEMENTATION */ |
| #endif /* TINYGLTF_JSON_C_H_ */ |