1/*
2 * Copyright © 2021 Benjamin Otte
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20
21#include "config.h"
22
23#include "gtkjsonprinterprivate.h"
24
25typedef struct _GtkJsonBlock GtkJsonBlock;
26
27typedef enum {
28 GTK_JSON_BLOCK_TOPLEVEL,
29 GTK_JSON_BLOCK_OBJECT,
30 GTK_JSON_BLOCK_ARRAY,
31} GtkJsonBlockType;
32
33struct _GtkJsonBlock
34{
35 GtkJsonBlockType type;
36 gsize n_elements; /* number of elements already written */
37};
38
39struct _GtkJsonPrinter
40{
41 GtkJsonPrinterFlags flags;
42 char *indentation;
43
44 GtkJsonPrinterWriteFunc write_func;
45 gpointer user_data;
46 GDestroyNotify user_destroy;
47
48 GtkJsonBlock *block; /* current block */
49 GtkJsonBlock *blocks; /* blocks array */
50 GtkJsonBlock *blocks_end; /* blocks array */
51 GtkJsonBlock blocks_preallocated[128]; /* preallocated */
52};
53
54static void
55gtk_json_printer_push_block (GtkJsonPrinter *self,
56 GtkJsonBlockType type)
57{
58 self->block++;
59 if (self->block == self->blocks_end)
60 {
61 gsize old_size = self->blocks_end - self->blocks;
62 gsize new_size = old_size + 128;
63
64 if (self->blocks == self->blocks_preallocated)
65 {
66 self->blocks = g_new (GtkJsonBlock, new_size);
67 memcpy (dest: self->blocks, src: self->blocks_preallocated, n: sizeof (GtkJsonBlock) * G_N_ELEMENTS (self->blocks_preallocated));
68 }
69 else
70 {
71 self->blocks = g_renew (GtkJsonBlock, self->blocks, new_size);
72 }
73 self->blocks_end = self->blocks + new_size;
74 self->block = self->blocks + old_size;
75 }
76
77 self->block->type = type;
78 self->block->n_elements = 0;
79}
80
81static void
82gtk_json_printer_pop_block (GtkJsonPrinter *self)
83{
84 g_assert (self->block > self->blocks);
85 self->block--;
86}
87
88GtkJsonPrinter *
89gtk_json_printer_new (GtkJsonPrinterWriteFunc write_func,
90 gpointer data,
91 GDestroyNotify destroy)
92{
93 GtkJsonPrinter *self;
94
95 g_return_val_if_fail (write_func, NULL);
96
97 self = g_slice_new0 (GtkJsonPrinter);
98
99 self->flags = 0;
100 self->indentation = g_strdup (str: " ");
101
102 self->write_func = write_func;
103 self->user_data = data;
104 self->user_destroy = destroy;
105
106 self->blocks = self->blocks_preallocated;
107 self->blocks_end = self->blocks + G_N_ELEMENTS (self->blocks_preallocated);
108 self->block = self->blocks;
109 self->block->type = GTK_JSON_BLOCK_TOPLEVEL;
110
111 return self;
112}
113
114void
115gtk_json_printer_free (GtkJsonPrinter *self)
116{
117 g_return_if_fail (self != NULL);
118
119 g_free (mem: self->indentation);
120
121 if (self->user_destroy)
122 self->user_destroy (self->user_data);
123
124 if (self->blocks != self->blocks_preallocated)
125 g_free (mem: self->blocks);
126
127 g_slice_free (GtkJsonPrinter, self);
128}
129
130static gboolean
131gtk_json_printer_has_flag (GtkJsonPrinter *self,
132 GtkJsonPrinterFlags flag)
133{
134 return (self->flags & flag) ? TRUE : FALSE;
135}
136
137gsize
138gtk_json_printer_get_depth (GtkJsonPrinter *self)
139{
140 return self->block - self->blocks;
141}
142
143gsize
144gtk_json_printer_get_n_elements (GtkJsonPrinter *self)
145{
146 return self->block->n_elements;
147}
148
149void
150gtk_json_printer_set_flags (GtkJsonPrinter *self,
151 GtkJsonPrinterFlags flags)
152{
153 g_return_if_fail (self != NULL);
154
155 self->flags = flags;
156}
157
158GtkJsonPrinterFlags
159gtk_json_printer_get_flags (GtkJsonPrinter *self)
160{
161 g_return_val_if_fail (self != NULL, 0);
162
163 return self->flags;
164}
165
166void
167gtk_json_printer_set_indentation (GtkJsonPrinter *self,
168 gsize amount)
169{
170 g_return_if_fail (self != NULL);
171
172 g_free (mem: self->indentation);
173
174 self->indentation = g_malloc (n_bytes: amount + 1);
175 memset (s: self->indentation, c: ' ', n: amount);
176 self->indentation[amount] = 0;
177}
178
179gsize
180gtk_json_printer_get_indentation (GtkJsonPrinter *self)
181{
182 g_return_val_if_fail (self != NULL, 2);
183
184 return strlen (s: self->indentation);
185}
186
187static void
188gtk_json_printer_write (GtkJsonPrinter *self,
189 const char *s)
190{
191 self->write_func (self, s, self->user_data);
192}
193
194static char *
195gtk_json_printer_escape_string (GtkJsonPrinter *self,
196 const char *str)
197{
198 GString *string;
199
200 string = g_string_new (NULL);
201 string = g_string_append_c (string, '"');
202
203 for (; *str != '\0'; str = g_utf8_next_char (str))
204 {
205 switch (*str)
206 {
207 case '"':
208 g_string_append (string, val: "\\\"");
209 break;
210 case '\\':
211 g_string_append (string, val: "\\\\");
212 break;
213 case '\b':
214 g_string_append (string, val: "\\b");
215 break;
216 case '\f':
217 g_string_append (string, val: "\\f");
218 break;
219 case '\n':
220 g_string_append (string, val: "\\n");
221 break;
222 case '\r':
223 g_string_append (string, val: "\\r");
224 break;
225 case '\t':
226 g_string_append (string, val: "\\t");
227 break;
228 default:
229 if ((int) *str < 0x20 || (int) *str >= 0x80)
230 {
231 if ((guint) *str < 0x20 || gtk_json_printer_has_flag (self, flag: GTK_JSON_PRINTER_ASCII))
232 g_string_append_printf (string, format: "\\u%04x", g_utf8_get_char (p: str));
233 else
234 g_string_append_unichar (string, wc: g_utf8_get_char (p: str));
235 }
236 else
237 g_string_append_c (string, *str);
238 }
239 }
240
241 string = g_string_append_c (string, '"');
242 return g_string_free (string, FALSE);
243}
244
245static void
246gtk_json_printer_newline (GtkJsonPrinter *self)
247{
248 gsize depth;
249
250 if (!gtk_json_printer_has_flag (self, flag: GTK_JSON_PRINTER_PRETTY))
251 return;
252
253 gtk_json_printer_write (self, s: "\n");
254 for (depth = gtk_json_printer_get_depth (self); depth-->0;)
255 gtk_json_printer_write (self, s: self->indentation);
256}
257
258static void
259gtk_json_printer_begin_member (GtkJsonPrinter *self,
260 const char *name)
261{
262 if (gtk_json_printer_get_n_elements (self) > 0)
263 gtk_json_printer_write (self, s: ",");
264 if (self->block->type != GTK_JSON_BLOCK_TOPLEVEL || gtk_json_printer_get_n_elements (self) > 0)
265 gtk_json_printer_newline (self);
266
267 self->block->n_elements++;
268
269 if (name)
270 {
271 char *escaped = gtk_json_printer_escape_string (self, str: name);
272 gtk_json_printer_write (self, s: escaped);
273 g_free (mem: escaped);
274 if (gtk_json_printer_has_flag (self, flag: GTK_JSON_PRINTER_PRETTY))
275 gtk_json_printer_write (self, s: " : ");
276 else
277 gtk_json_printer_write (self, s: ":");
278 }
279}
280
281void
282gtk_json_printer_add_boolean (GtkJsonPrinter *self,
283 const char *name,
284 gboolean value)
285{
286 g_return_if_fail (self != NULL);
287 g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
288
289 gtk_json_printer_begin_member (self, name);
290 gtk_json_printer_write (self, s: value ? "true" : "false");
291}
292
293void
294gtk_json_printer_add_number (GtkJsonPrinter *self,
295 const char *name,
296 double value)
297{
298 char buf[G_ASCII_DTOSTR_BUF_SIZE];
299
300 g_return_if_fail (self != NULL);
301 g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
302
303 gtk_json_printer_begin_member (self, name);
304 g_ascii_dtostr (buffer: buf, G_ASCII_DTOSTR_BUF_SIZE, d: value);
305 gtk_json_printer_write (self, s: buf);
306}
307
308void
309gtk_json_printer_add_integer (GtkJsonPrinter *self,
310 const char *name,
311 int value)
312{
313 char buf[128];
314
315 g_return_if_fail (self != NULL);
316 g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
317
318 gtk_json_printer_begin_member (self, name);
319 g_snprintf (string: buf, n: sizeof (buf), format: "%d", value);
320 gtk_json_printer_write (self, s: buf);
321}
322
323void
324gtk_json_printer_add_string (GtkJsonPrinter *self,
325 const char *name,
326 const char *s)
327{
328 char *escaped;
329
330 g_return_if_fail (self != NULL);
331 g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
332 g_return_if_fail (s != NULL);
333
334 gtk_json_printer_begin_member (self, name);
335 escaped = gtk_json_printer_escape_string (self, str: s);
336 gtk_json_printer_write (self, s: escaped);
337 g_free (mem: escaped);
338}
339
340void
341gtk_json_printer_add_null (GtkJsonPrinter *self,
342 const char *name)
343{
344 g_return_if_fail (self != NULL);
345 g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
346
347 gtk_json_printer_begin_member (self, name);
348 gtk_json_printer_write (self, s: "null");
349}
350
351void
352gtk_json_printer_start_object (GtkJsonPrinter *self,
353 const char *name)
354{
355 g_return_if_fail (self != NULL);
356 g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
357
358 gtk_json_printer_begin_member (self, name);
359 gtk_json_printer_write (self, s: "{");
360 gtk_json_printer_push_block (self, type: GTK_JSON_BLOCK_OBJECT);
361}
362
363void
364gtk_json_printer_start_array (GtkJsonPrinter *self,
365 const char *name)
366{
367 g_return_if_fail (self != NULL);
368 g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
369
370 gtk_json_printer_begin_member (self, name);
371 gtk_json_printer_write (self, s: "[");
372 gtk_json_printer_push_block (self, type: GTK_JSON_BLOCK_ARRAY);
373}
374
375void
376gtk_json_printer_end (GtkJsonPrinter *self)
377{
378 const char *bracket;
379 gboolean empty;
380
381 g_return_if_fail (self != NULL);
382
383 switch (self->block->type)
384 {
385 case GTK_JSON_BLOCK_OBJECT:
386 bracket = "}";
387 break;
388 case GTK_JSON_BLOCK_ARRAY:
389 bracket = "]";
390 break;
391 case GTK_JSON_BLOCK_TOPLEVEL:
392 default:
393 g_return_if_reached ();
394 }
395
396 empty = gtk_json_printer_get_n_elements (self) == 0;
397 gtk_json_printer_pop_block (self);
398
399 if (!empty)
400 {
401 gtk_json_printer_newline (self);
402 }
403 gtk_json_printer_write (self, s: bracket);
404}
405
406

source code of gtk/subprojects/pango/pango/json/gtkjsonprinter.c