/* * Copyright © 2021 Benjamin Otte * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: Benjamin Otte */ #include "config.h" #include "gtkjsonparserprivate.h" typedef struct _GtkJsonBlock GtkJsonBlock; typedef enum { GTK_JSON_BLOCK_TOPLEVEL, GTK_JSON_BLOCK_OBJECT, GTK_JSON_BLOCK_ARRAY, } GtkJsonBlockType; struct _GtkJsonBlock { GtkJsonBlockType type; const guchar *value; /* start of current value to be consumed by external code */ const guchar *member_name; /* name of current value, only used for object types */ gsize index; /* index of the current element */ }; struct _GtkJsonParser { GBytes *bytes; const guchar *reader; /* current read head, pointing as far as we've read */ const guchar *end; /* pointer after end of data we're reading */ GError *error; /* if an error has happened, it's stored here. Errors aren't recoverable. */ GtkJsonBlock *block; /* current block */ GtkJsonBlock *blocks; /* blocks array */ GtkJsonBlock *blocks_end; /* blocks array */ GtkJsonBlock blocks_preallocated[128]; /* preallocated */ }; typedef enum { WHITESPACE = (1 << 4), STRING_ELEMENT = (1 << 5), STRING_MARKER = (1 << 6), } JsonCharacterType; #define JSON_CHARACTER_NODE_MASK ((1 << 4) - 1) static const guchar json_character_table[256] = { ['\t'] = WHITESPACE, ['\r'] = WHITESPACE, ['\n'] = WHITESPACE, [' '] = WHITESPACE | STRING_ELEMENT, ['!'] = STRING_ELEMENT, ['"'] = GTK_JSON_STRING | STRING_MARKER, ['#'] = STRING_ELEMENT, ['$'] = STRING_ELEMENT, ['%'] = STRING_ELEMENT, ['&'] = STRING_ELEMENT, ['\''] = STRING_ELEMENT, ['('] = STRING_ELEMENT, [')'] = STRING_ELEMENT, ['*'] = STRING_ELEMENT, ['+'] = STRING_ELEMENT, [','] = STRING_ELEMENT, ['-'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['.'] = STRING_ELEMENT, ['/'] = STRING_ELEMENT, ['0'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['1'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['2'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['3'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['4'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['5'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['6'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['7'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['8'] = GTK_JSON_NUMBER | STRING_ELEMENT, ['9'] = GTK_JSON_NUMBER | STRING_ELEMENT, [':'] = STRING_ELEMENT, [';'] = STRING_ELEMENT, ['<'] = STRING_ELEMENT, ['='] = STRING_ELEMENT, ['>'] = STRING_ELEMENT, ['?'] = STRING_ELEMENT, ['@'] = STRING_ELEMENT, ['A'] = STRING_ELEMENT, ['B'] = STRING_ELEMENT, ['C'] = STRING_ELEMENT, ['D'] = STRING_ELEMENT, ['E'] = STRING_ELEMENT, ['F'] = STRING_ELEMENT, ['G'] = STRING_ELEMENT, ['H'] = STRING_ELEMENT, ['I'] = STRING_ELEMENT, ['J'] = STRING_ELEMENT, ['K'] = STRING_ELEMENT, ['L'] = STRING_ELEMENT, ['M'] = STRING_ELEMENT, ['N'] = STRING_ELEMENT, ['O'] = STRING_ELEMENT, ['P'] = STRING_ELEMENT, ['Q'] = STRING_ELEMENT, ['R'] = STRING_ELEMENT, ['S'] = STRING_ELEMENT, ['T'] = STRING_ELEMENT, ['U'] = STRING_ELEMENT, ['V'] = STRING_ELEMENT, ['W'] = STRING_ELEMENT, ['X'] = STRING_ELEMENT, ['Y'] = STRING_ELEMENT, ['Z'] = STRING_ELEMENT, ['['] = GTK_JSON_ARRAY | STRING_ELEMENT, ['\\'] = STRING_MARKER, [']'] = STRING_ELEMENT, ['^'] = STRING_ELEMENT, ['_'] = STRING_ELEMENT, ['`'] = STRING_ELEMENT, ['a'] = STRING_ELEMENT, ['b'] = STRING_ELEMENT, ['c'] = STRING_ELEMENT, ['d'] = STRING_ELEMENT, ['e'] = STRING_ELEMENT, ['f'] = GTK_JSON_BOOLEAN | STRING_ELEMENT, ['g'] = STRING_ELEMENT, ['h'] = STRING_ELEMENT, ['i'] = STRING_ELEMENT, ['j'] = STRING_ELEMENT, ['k'] = STRING_ELEMENT, ['l'] = STRING_ELEMENT, ['m'] = STRING_ELEMENT, ['n'] = GTK_JSON_NULL | STRING_ELEMENT, ['o'] = STRING_ELEMENT, ['p'] = STRING_ELEMENT, ['q'] = STRING_ELEMENT, ['r'] = STRING_ELEMENT, ['s'] = STRING_ELEMENT, ['t'] = GTK_JSON_BOOLEAN | STRING_ELEMENT, ['u'] = STRING_ELEMENT, ['v'] = STRING_ELEMENT, ['w'] = STRING_ELEMENT, ['x'] = STRING_ELEMENT, ['y'] = STRING_ELEMENT, ['z'] = STRING_ELEMENT, ['{'] = GTK_JSON_OBJECT | STRING_ELEMENT, ['|'] = STRING_ELEMENT, ['}'] = STRING_ELEMENT, ['~'] = STRING_ELEMENT, [127] = STRING_ELEMENT, }; static const guchar * json_skip_characters (const guchar *start, const guchar *end, JsonCharacterType type) { const guchar *s; for (s = start; s < end; s++) { if (!(json_character_table[*s] & type)) break; } return s; } static const guchar * json_find_character (const guchar *start, JsonCharacterType type) { const guchar *s; for (s = start; ; s++) { if ((json_character_table[*s] & type)) break; } return s; } static void gtk_json_parser_value_error (GtkJsonParser *self, const char *format, ...) G_GNUC_PRINTF(2, 3); static void gtk_json_parser_value_error (GtkJsonParser *self, const char *format, ...) { va_list args; if (self->error) return; va_start (args, format); self->error = g_error_new_valist (G_FILE_ERROR, G_FILE_ERROR_FAILED, format, args); va_end (args); } static void gtk_json_parser_syntax_error (GtkJsonParser *self, const char *format, ...) G_GNUC_PRINTF(2, 3); static void gtk_json_parser_syntax_error (GtkJsonParser *self, const char *format, ...) { va_list args; if (self->error) return; va_start (args, format); self->error = g_error_new_valist (G_FILE_ERROR, G_FILE_ERROR_FAILED, format, args); va_end (args); } static gboolean gtk_json_parser_is_eof (GtkJsonParser *self) { return self->reader >= self->end || *self->reader == '\0'; } static gsize gtk_json_parser_remaining (GtkJsonParser *self) { g_return_val_if_fail (self->reader <= self->end, 0); return self->end - self->reader; } static void gtk_json_parser_skip_whitespace (GtkJsonParser *self) { self->reader = json_skip_characters (self->reader, self->end, WHITESPACE); } static gboolean gtk_json_parser_has_char (GtkJsonParser *self, char c) { return gtk_json_parser_remaining (self) && *self->reader == c; } static gboolean gtk_json_parser_try_char (GtkJsonParser *self, char c) { if (!gtk_json_parser_has_char (self, c)) return FALSE; self->reader++; return TRUE; } static gboolean gtk_json_parser_try_identifier_len (GtkJsonParser *self, const char *ident, gsize len) { if (gtk_json_parser_remaining (self) < len) return FALSE; if (memcmp (self->reader, ident, len) != 0) return FALSE; self->reader += len; return TRUE; } #define gtk_json_parser_try_identifier(parser, ident) gtk_json_parser_try_identifier_len(parser, ident, strlen(ident)) /* * decode_utf16_surrogate_pair: * @first: the first UTF-16 code point * @second: the second UTF-16 code point * * Decodes a surrogate pair of UTF-16 code points into the equivalent * Unicode code point. * * If the code points are not valid, 0 is returned. * * Returns: the Unicode code point equivalent to the surrogate pair */ static inline gunichar decode_utf16_surrogate_pair (gunichar first, gunichar second) { if (0xd800 > first || first > 0xdbff || 0xdc00 > second || second > 0xdfff) return 0; return 0x10000 | (first & 0x3ff) << 10 | (second & 0x3ff); } static gsize gtk_json_unescape_char (const guchar *json_escape, char out_data[6], gsize *out_len) { switch (json_escape[1]) { case '"': case '\\': case '/': out_data[0] = json_escape[1]; *out_len = 1; return 2; case 'b': out_data[0] = '\b'; *out_len = 1; return 2; case 'f': out_data[0] = '\f'; *out_len = 1; return 2; case 'n': out_data[0] = '\n'; *out_len = 1; return 2; case 'r': out_data[0] = '\r'; *out_len = 1; return 2; case 't': out_data[0] = '\t'; *out_len = 1; return 2; case 'u': { gunichar unichar = (g_ascii_xdigit_value (json_escape[2]) << 12) | (g_ascii_xdigit_value (json_escape[3]) << 8) | (g_ascii_xdigit_value (json_escape[4]) << 4) | (g_ascii_xdigit_value (json_escape[5])); gsize result = 6; /* resolve UTF-16 surrogates for Unicode characters not in the BMP, * as per ECMA 404, § 9, "String" */ if (g_unichar_type (unichar) == G_UNICODE_SURROGATE) { unichar = decode_utf16_surrogate_pair (unichar, (g_ascii_xdigit_value (json_escape[8]) << 12) | (g_ascii_xdigit_value (json_escape[9]) << 8) | (g_ascii_xdigit_value (json_escape[10]) << 4) | (g_ascii_xdigit_value (json_escape[11]))); result += 6; } *out_len = g_unichar_to_utf8 (unichar, out_data); return result; } default: g_assert_not_reached (); return 0; } } /* The escaped string MUST be valid json, so it must begin * with " and end with " and must not contain any invalid * escape codes. * This function is meant to be fast */ static char * gtk_json_unescape_string (const guchar *escaped) { char buf[6]; gsize buf_size; GString *string; const guchar *last, *s; string = NULL; g_assert (*escaped == '"'); last = escaped + 1; for (s = json_find_character (last, STRING_MARKER); *s != '"'; s = json_find_character (last, STRING_MARKER)) { g_assert (*s == '\\'); if (string == NULL) string = g_string_new (NULL); g_string_append_len (string, (const char *) last, s - last); last = s + gtk_json_unescape_char (s, buf, &buf_size); g_string_append_len (string, buf, buf_size); } if (string) { g_string_append_len (string, (const char *) last, s - last); return g_string_free (string, FALSE); } else { return g_strndup ((const char *) last, s - last); } } static gboolean gtk_json_parser_parse_string (GtkJsonParser *self) { if (!gtk_json_parser_try_char (self, '"')) { gtk_json_parser_syntax_error (self, "Not a string"); return FALSE; } self->reader = json_skip_characters (self->reader, self->end, STRING_ELEMENT); while (gtk_json_parser_remaining (self)) { if (*self->reader < 0x20) { gtk_json_parser_syntax_error (self, "Disallowed control character in string literal"); return FALSE; } else if (*self->reader > 127) { gunichar c = g_utf8_get_char_validated ((const char *) self->reader, gtk_json_parser_remaining (self)); if (c == (gunichar) -2 || c == (gunichar) -1) { gtk_json_parser_syntax_error (self, "Invalid UTF-8"); return FALSE; } self->reader = (const guchar *) g_utf8_next_char ((const char *) self->reader); } else if (*self->reader == '"') { self->reader++; return TRUE; } else if (*self->reader == '\\') { if (gtk_json_parser_remaining (self) < 2) goto end; self->reader++; switch (*self->reader) { case '"': case '\\': case '/': case 'b': case 'f': case 'n': case 'r': case 't': break; case 'u': /* lots of work necessary to validate the unicode escapes here */ if (gtk_json_parser_remaining (self) < 5 || !g_ascii_isxdigit (self->reader[1]) || !g_ascii_isxdigit (self->reader[2]) || !g_ascii_isxdigit (self->reader[3]) || !g_ascii_isxdigit (self->reader[4])) { gtk_json_parser_syntax_error (self, "Invalid Unicode escape sequence"); return FALSE; } else { gunichar unichar = (g_ascii_xdigit_value (self->reader[1]) << 12) | (g_ascii_xdigit_value (self->reader[2]) << 8) | (g_ascii_xdigit_value (self->reader[3]) << 4) | (g_ascii_xdigit_value (self->reader[4])); self->reader += 4; /* resolve UTF-16 surrogates for Unicode characters not in the BMP, * as per ECMA 404, § 9, "String" */ if (g_unichar_type (unichar) == G_UNICODE_SURROGATE) { if (gtk_json_parser_remaining (self) >= 7 && self->reader[1] == '\\' && self->reader[2] == 'u' && g_ascii_isxdigit (self->reader[3]) && g_ascii_isxdigit (self->reader[4]) && g_ascii_isxdigit (self->reader[5]) && g_ascii_isxdigit (self->reader[6])) { unichar = decode_utf16_surrogate_pair (unichar, (g_ascii_xdigit_value (self->reader[3]) << 12) | (g_ascii_xdigit_value (self->reader[4]) << 8) | (g_ascii_xdigit_value (self->reader[5]) << 4) | (g_ascii_xdigit_value (self->reader[6]))); self->reader += 6; } else { unichar = 0; } } if (unichar == 0) { gtk_json_parser_syntax_error (self, "Invalid UTF-16 surrogate pair"); return FALSE; } } break; default: gtk_json_parser_syntax_error (self, "Unknown escape sequence"); return FALSE; } self->reader++; } self->reader = json_skip_characters (self->reader, self->end, STRING_ELEMENT); } end: gtk_json_parser_syntax_error (self, "Unterminated string literal"); return FALSE; } static gboolean gtk_json_parser_parse_number (GtkJsonParser *self) { /* sign */ gtk_json_parser_try_char (self, '-'); /* integer part */ if (!gtk_json_parser_try_char (self, '0')) { if (gtk_json_parser_is_eof (self) || !g_ascii_isdigit (*self->reader)) goto out; self->reader++; while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader)) self->reader++; } /* fractional part */ if (gtk_json_parser_remaining (self) >= 2 && *self->reader == '.' && g_ascii_isdigit (self->reader[1])) { self->reader += 2; while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader)) self->reader++; } /* exponent */ if (gtk_json_parser_remaining (self) >= 2 && (self->reader[0] == 'e' || self->reader[0] == 'E') && (g_ascii_isdigit (self->reader[1]) || (gtk_json_parser_remaining (self) >= 3 && (self->reader[1] == '+' || self->reader[1] == '-') && g_ascii_isdigit (self->reader[2])))) { self->reader += 2; while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader)) self->reader++; } return TRUE; out: gtk_json_parser_syntax_error (self, "Not a valid number"); return FALSE; } static gboolean gtk_json_parser_parse_value (GtkJsonParser *self) { if (gtk_json_parser_is_eof (self)) { gtk_json_parser_syntax_error (self, "Unexpected end of document"); return FALSE; } switch (json_character_table[*self->block->value] & JSON_CHARACTER_NODE_MASK) { case GTK_JSON_STRING: return gtk_json_parser_parse_string (self); case GTK_JSON_NUMBER: return gtk_json_parser_parse_number (self); case GTK_JSON_NULL: if (gtk_json_parser_try_identifier (self, "null")) return TRUE; break; case GTK_JSON_BOOLEAN: if (gtk_json_parser_try_identifier (self, "true") || gtk_json_parser_try_identifier (self, "false")) return TRUE; break; case GTK_JSON_OBJECT: case GTK_JSON_ARRAY: /* don't preparse objects */ return TRUE; default: break; } gtk_json_parser_syntax_error (self, "Expected a value"); return FALSE; } static void gtk_json_parser_push_block (GtkJsonParser *self, GtkJsonBlockType type) { self->block++; if (self->block == self->blocks_end) { gsize old_size = self->blocks_end - self->blocks; gsize new_size = old_size + 128; if (self->blocks == self->blocks_preallocated) { self->blocks = g_new (GtkJsonBlock, new_size); memcpy (self->blocks, self->blocks_preallocated, sizeof (GtkJsonBlock) * G_N_ELEMENTS (self->blocks_preallocated)); } else { self->blocks = g_renew (GtkJsonBlock, self->blocks, new_size); } self->blocks_end = self->blocks + new_size; self->block = self->blocks + old_size; } self->block->type = type; self->block->member_name = 0; self->block->value = 0; self->block->index = 0; } static void gtk_json_parser_pop_block (GtkJsonParser *self) { g_assert (self->block > self->blocks); self->block--; } GtkJsonParser * gtk_json_parser_new_for_string (const char *string, gssize size) { GtkJsonParser *self; GBytes *bytes; bytes = g_bytes_new (string, size >= 0 ? size : strlen (string)); self = gtk_json_parser_new_for_bytes (bytes); g_bytes_unref (bytes); return self; } GtkJsonParser * gtk_json_parser_new_for_bytes (GBytes *bytes) { GtkJsonParser *self; gsize size; g_return_val_if_fail (bytes != NULL, NULL); self = g_slice_new0 (GtkJsonParser); self->bytes = g_bytes_ref (bytes); self->reader = g_bytes_get_data (bytes, &size); self->end = self->reader + size; self->blocks = self->blocks_preallocated; self->blocks_end = self->blocks + G_N_ELEMENTS (self->blocks_preallocated); self->block = self->blocks; self->block->type = GTK_JSON_BLOCK_TOPLEVEL; gtk_json_parser_skip_whitespace (self); self->block->value = self->reader; gtk_json_parser_parse_value (self); return self; } void gtk_json_parser_free (GtkJsonParser *self) { if (self == NULL) return; g_clear_error (&self->error); g_bytes_unref (self->bytes); if (self->blocks != self->blocks_preallocated) g_free (self->blocks); g_slice_free (GtkJsonParser, self); } static gboolean gtk_json_parser_skip_block (GtkJsonParser *self) { if (self->reader != self->block->value) return TRUE; if (*self->reader == '{') { return gtk_json_parser_start_object (self) && gtk_json_parser_end (self); } else if (*self->reader == '[') { return gtk_json_parser_start_array (self) && gtk_json_parser_end (self); } else { g_assert_not_reached (); return FALSE; } } gboolean gtk_json_parser_next (GtkJsonParser *self) { if (self->error) return FALSE; if (self->block->value == NULL) return FALSE; if (!gtk_json_parser_skip_block (self)) { g_assert (self->error); return FALSE; } switch (self->block->type) { case GTK_JSON_BLOCK_TOPLEVEL: gtk_json_parser_skip_whitespace (self); if (gtk_json_parser_is_eof (self)) { self->block->value = NULL; if (gtk_json_parser_remaining (self)) { gtk_json_parser_syntax_error (self, "Unexpected nul byte in document"); } } else { gtk_json_parser_syntax_error (self, "Data at end of document"); } return FALSE; case GTK_JSON_BLOCK_OBJECT: gtk_json_parser_skip_whitespace (self); if (gtk_json_parser_is_eof (self)) { gtk_json_parser_syntax_error (self, "Unexpected end of document"); self->block->member_name = NULL; self->block->value = NULL; } if (gtk_json_parser_has_char (self, '}')) { self->block->member_name = NULL; self->block->value = NULL; return FALSE; } if (!gtk_json_parser_try_char (self, ',')) { gtk_json_parser_syntax_error (self, "Expected a ',' to separate object members"); return FALSE; } gtk_json_parser_skip_whitespace (self); self->block->member_name = self->reader; if (!gtk_json_parser_parse_string (self)) return FALSE; gtk_json_parser_skip_whitespace (self); if (!gtk_json_parser_try_char (self, ':')) { gtk_json_parser_syntax_error (self, "Missing ':' after member name"); return FALSE; } gtk_json_parser_skip_whitespace (self); self->block->value = self->reader; if (!gtk_json_parser_parse_value (self)) return FALSE; break; case GTK_JSON_BLOCK_ARRAY: gtk_json_parser_skip_whitespace (self); if (gtk_json_parser_is_eof (self)) { gtk_json_parser_syntax_error (self, "Unexpected end of document"); self->block->member_name = NULL; self->block->value = NULL; } if (gtk_json_parser_has_char (self, ']')) { self->block->value = NULL; return FALSE; } if (!gtk_json_parser_try_char (self, ',')) { gtk_json_parser_syntax_error (self, "Expected a ',' to separate array members"); return FALSE; } gtk_json_parser_skip_whitespace (self); self->block->value = self->reader; if (!gtk_json_parser_parse_value (self)) return FALSE; break; default: g_assert_not_reached (); break; } return TRUE; } GtkJsonNode gtk_json_parser_get_node (GtkJsonParser *self) { if (self->error) return GTK_JSON_NONE; if (self->block->value == NULL) return GTK_JSON_NONE; return (json_character_table[*self->block->value] & JSON_CHARACTER_NODE_MASK); } const GError * gtk_json_parser_get_error (GtkJsonParser *self) { return self->error; } char * gtk_json_parser_get_member_name (GtkJsonParser *self) { if (self->error) return NULL; if (self->block->type != GTK_JSON_BLOCK_OBJECT) return NULL; if (self->block->member_name == NULL) return NULL; return gtk_json_unescape_string (self->block->member_name); } gssize gtk_json_parser_select_member (GtkJsonParser *self, const char * const *options) { char *member_name; gssize i; member_name = gtk_json_parser_get_member_name (self); if (member_name == NULL) return -1; for (i = 0; options[i]; i++) { if (strcmp (member_name, options[i]) == 0) break; } if (options[i] == NULL) i = -1; g_free (member_name); return i; } gboolean gtk_json_parser_get_boolean (GtkJsonParser *self) { if (self->error) return FALSE; if (self->block->value == NULL) return FALSE; if (*self->block->value == 't') return TRUE; else if (*self->block->value == 'f') return FALSE; gtk_json_parser_value_error (self, "Expected a boolean value"); return FALSE; } double gtk_json_parser_get_number (GtkJsonParser *self) { double result; if (self->error) return 0; if (self->block->value == NULL) return 0; if (!strchr ("-0123456789", *self->block->value)) { gtk_json_parser_value_error (self, "Expected a number"); return 0; } errno = 0; if (gtk_json_parser_remaining (self) == 0) { /* need terminated string here */ char *s = g_strndup ((const char *) self->block->value, self->reader - self->block->value); result = g_ascii_strtod (s, NULL); g_free (s); } else { result = g_ascii_strtod ((const char *) self->block->value, NULL); } if (errno) { if (errno == ERANGE) gtk_json_parser_value_error (self, "Number out of range"); else gtk_json_parser_value_error (self, "%s", g_strerror (errno)); return 0; } return result; } int gtk_json_parser_get_int (GtkJsonParser *self) { int result; if (self->error) return 0; if (self->block->value == NULL) return 0; if (!strchr ("-0123456789", *self->block->value)) { gtk_json_parser_value_error (self, "Expected a number"); return 0; } errno = 0; if (gtk_json_parser_remaining (self) == 0) { /* need terminated string here */ char *s = g_strndup ((const char *) self->block->value, self->reader - self->block->value); result = g_ascii_strtod (s, NULL); g_free (s); } else { result = (int) g_ascii_strtoll ((const char *) self->block->value, NULL, 10); } if (errno) { if (errno == ERANGE) gtk_json_parser_value_error (self, "Number out of range"); else gtk_json_parser_value_error (self, "%s", g_strerror (errno)); return 0; } return result; } #if 0 int gtk_json_parser_get_int (GtkJsonParser *self); guint gtk_json_parser_get_uint (GtkJsonParser *self); #endif char * gtk_json_parser_get_string (GtkJsonParser *self) { if (self->error) return g_strdup (""); if (self->block->value == NULL) return g_strdup (""); if (*self->block->value != '"') { gtk_json_parser_value_error (self, "Expected a string"); return g_strdup (""); } return gtk_json_unescape_string (self->block->value); } gboolean gtk_json_parser_start_object (GtkJsonParser *self) { if (self->error) return FALSE; if (!gtk_json_parser_try_char (self, '{')) { gtk_json_parser_value_error (self, "Expected an object"); return FALSE; } gtk_json_parser_push_block (self, GTK_JSON_BLOCK_OBJECT); gtk_json_parser_skip_whitespace (self); if (gtk_json_parser_is_eof (self)) { gtk_json_parser_syntax_error (self, "Unexpected end of document"); return FALSE; } if (gtk_json_parser_has_char (self, '}')) return TRUE; self->block->member_name = self->reader; if (!gtk_json_parser_parse_string (self)) return FALSE; gtk_json_parser_skip_whitespace (self); if (!gtk_json_parser_try_char (self, ':')) { gtk_json_parser_syntax_error (self, "Missing ':' after member name"); return FALSE; } gtk_json_parser_skip_whitespace (self); self->block->value = self->reader; if (!gtk_json_parser_parse_value (self)) return FALSE; return TRUE; } gboolean gtk_json_parser_start_array (GtkJsonParser *self) { if (self->error) return FALSE; if (!gtk_json_parser_try_char (self, '[')) { gtk_json_parser_value_error (self, "Expected an array"); return FALSE; } gtk_json_parser_push_block (self, GTK_JSON_BLOCK_ARRAY); gtk_json_parser_skip_whitespace (self); if (gtk_json_parser_is_eof (self)) { gtk_json_parser_syntax_error (self, "Unexpected end of document"); return FALSE; } if (gtk_json_parser_has_char (self, ']')) { self->block->value = NULL; return TRUE; } self->block->value = self->reader; if (!gtk_json_parser_parse_value (self)) return FALSE; return TRUE; } gboolean gtk_json_parser_end (GtkJsonParser *self) { char bracket; g_return_val_if_fail (self != NULL, FALSE); while (gtk_json_parser_next (self)); if (self->error) return FALSE; switch (self->block->type) { case GTK_JSON_BLOCK_OBJECT: bracket = '}'; break; case GTK_JSON_BLOCK_ARRAY: bracket = ']'; break; case GTK_JSON_BLOCK_TOPLEVEL: default: g_return_val_if_reached (FALSE); } if (!gtk_json_parser_try_char (self, bracket)) { gtk_json_parser_syntax_error (self, "No terminating '%c'", bracket); return FALSE; } gtk_json_parser_pop_block (self); return TRUE; } void gtk_json_parser_set_error (GtkJsonParser *self, GError *error) { if (self->error) g_error_free (error); else self->error = error; }