/* * 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 "gtkjsonprinterprivate.h" typedef struct _GtkJsonBlock GtkJsonBlock; typedef enum { GTK_JSON_BLOCK_TOPLEVEL, GTK_JSON_BLOCK_OBJECT, GTK_JSON_BLOCK_ARRAY, } GtkJsonBlockType; struct _GtkJsonBlock { GtkJsonBlockType type; gsize n_elements; /* number of elements already written */ }; struct _GtkJsonPrinter { GtkJsonPrinterFlags flags; char *indentation; GtkJsonPrinterWriteFunc write_func; gpointer user_data; GDestroyNotify user_destroy; GtkJsonBlock *block; /* current block */ GtkJsonBlock *blocks; /* blocks array */ GtkJsonBlock *blocks_end; /* blocks array */ GtkJsonBlock blocks_preallocated[128]; /* preallocated */ }; static void gtk_json_printer_push_block (GtkJsonPrinter *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->n_elements = 0; } static void gtk_json_printer_pop_block (GtkJsonPrinter *self) { g_assert (self->block > self->blocks); self->block--; } GtkJsonPrinter * gtk_json_printer_new (GtkJsonPrinterWriteFunc write_func, gpointer data, GDestroyNotify destroy) { GtkJsonPrinter *self; g_return_val_if_fail (write_func, NULL); self = g_slice_new0 (GtkJsonPrinter); self->flags = 0; self->indentation = g_strdup (" "); self->write_func = write_func; self->user_data = data; self->user_destroy = destroy; 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; return self; } void gtk_json_printer_free (GtkJsonPrinter *self) { g_return_if_fail (self != NULL); g_free (self->indentation); if (self->user_destroy) self->user_destroy (self->user_data); if (self->blocks != self->blocks_preallocated) g_free (self->blocks); g_slice_free (GtkJsonPrinter, self); } static gboolean gtk_json_printer_has_flag (GtkJsonPrinter *self, GtkJsonPrinterFlags flag) { return (self->flags & flag) ? TRUE : FALSE; } gsize gtk_json_printer_get_depth (GtkJsonPrinter *self) { return self->block - self->blocks; } gsize gtk_json_printer_get_n_elements (GtkJsonPrinter *self) { return self->block->n_elements; } void gtk_json_printer_set_flags (GtkJsonPrinter *self, GtkJsonPrinterFlags flags) { g_return_if_fail (self != NULL); self->flags = flags; } GtkJsonPrinterFlags gtk_json_printer_get_flags (GtkJsonPrinter *self) { g_return_val_if_fail (self != NULL, 0); return self->flags; } void gtk_json_printer_set_indentation (GtkJsonPrinter *self, gsize amount) { g_return_if_fail (self != NULL); g_free (self->indentation); self->indentation = g_malloc (amount + 1); memset (self->indentation, ' ', amount); self->indentation[amount] = 0; } gsize gtk_json_printer_get_indentation (GtkJsonPrinter *self) { g_return_val_if_fail (self != NULL, 2); return strlen (self->indentation); } static void gtk_json_printer_write (GtkJsonPrinter *self, const char *s) { self->write_func (self, s, self->user_data); } static char * gtk_json_printer_escape_string (GtkJsonPrinter *self, const char *str) { GString *string; string = g_string_new (NULL); string = g_string_append_c (string, '"'); for (; *str != '\0'; str = g_utf8_next_char (str)) { switch (*str) { case '"': g_string_append (string, "\\\""); break; case '\\': g_string_append (string, "\\\\"); break; case '\b': g_string_append (string, "\\b"); break; case '\f': g_string_append (string, "\\f"); break; case '\n': g_string_append (string, "\\n"); break; case '\r': g_string_append (string, "\\r"); break; case '\t': g_string_append (string, "\\t"); break; default: if ((int) *str < 0x20) { if ((guint) *str < 0x20 || gtk_json_printer_has_flag (self, GTK_JSON_PRINTER_ASCII)) g_string_append_printf (string, "\\u%04x", g_utf8_get_char (str)); else g_string_append_unichar (string, g_utf8_get_char (str)); } else g_string_append_c (string, *str); } } string = g_string_append_c (string, '"'); return g_string_free (string, FALSE); } static void gtk_json_printer_newline (GtkJsonPrinter *self) { gsize depth; if (!gtk_json_printer_has_flag (self, GTK_JSON_PRINTER_PRETTY)) return; gtk_json_printer_write (self, "\n"); for (depth = gtk_json_printer_get_depth (self); depth-->0;) gtk_json_printer_write (self, self->indentation); } static void gtk_json_printer_begin_member (GtkJsonPrinter *self, const char *name) { if (gtk_json_printer_get_n_elements (self) > 0) gtk_json_printer_write (self, ","); if (self->block->type != GTK_JSON_BLOCK_TOPLEVEL || gtk_json_printer_get_n_elements (self) > 0) gtk_json_printer_newline (self); self->block->n_elements++; if (name) { char *escaped = gtk_json_printer_escape_string (self, name); gtk_json_printer_write (self, escaped); g_free (escaped); if (gtk_json_printer_has_flag (self, GTK_JSON_PRINTER_PRETTY)) gtk_json_printer_write (self, " : "); else gtk_json_printer_write (self, ":"); } } void gtk_json_printer_add_boolean (GtkJsonPrinter *self, const char *name, gboolean value) { g_return_if_fail (self != NULL); g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL)); gtk_json_printer_begin_member (self, name); gtk_json_printer_write (self, value ? "true" : "false"); } void gtk_json_printer_add_number (GtkJsonPrinter *self, const char *name, double value) { char buf[G_ASCII_DTOSTR_BUF_SIZE]; g_return_if_fail (self != NULL); g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL)); gtk_json_printer_begin_member (self, name); g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, value); gtk_json_printer_write (self, buf); } void gtk_json_printer_add_integer (GtkJsonPrinter *self, const char *name, int value) { char buf[128]; g_return_if_fail (self != NULL); g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL)); gtk_json_printer_begin_member (self, name); g_snprintf (buf, sizeof (buf), "%d", value); gtk_json_printer_write (self, buf); } void gtk_json_printer_add_string (GtkJsonPrinter *self, const char *name, const char *s) { char *escaped; g_return_if_fail (self != NULL); g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL)); g_return_if_fail (s != NULL); gtk_json_printer_begin_member (self, name); escaped = gtk_json_printer_escape_string (self, s); gtk_json_printer_write (self, escaped); g_free (escaped); } void gtk_json_printer_add_null (GtkJsonPrinter *self, const char *name) { g_return_if_fail (self != NULL); g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL)); gtk_json_printer_begin_member (self, name); gtk_json_printer_write (self, "null"); } void gtk_json_printer_start_object (GtkJsonPrinter *self, const char *name) { g_return_if_fail (self != NULL); g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL)); gtk_json_printer_begin_member (self, name); gtk_json_printer_write (self, "{"); gtk_json_printer_push_block (self, GTK_JSON_BLOCK_OBJECT); } void gtk_json_printer_start_array (GtkJsonPrinter *self, const char *name) { g_return_if_fail (self != NULL); g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL)); gtk_json_printer_begin_member (self, name); gtk_json_printer_write (self, "["); gtk_json_printer_push_block (self, GTK_JSON_BLOCK_ARRAY); } void gtk_json_printer_end (GtkJsonPrinter *self) { const char *bracket; gboolean empty; g_return_if_fail (self != NULL); 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_if_reached (); } empty = gtk_json_printer_get_n_elements (self) == 0; gtk_json_printer_pop_block (self); if (!empty) { gtk_json_printer_newline (self); } gtk_json_printer_write (self, bracket); }