Compare commits

...

24 Commits

Author SHA1 Message Date
Benjamin Otte
167b19edfc testsuite: Add JSON tests fuzzers complained about 2023-04-15 06:11:54 +02:00
Benjamin Otte
7f43ff0fe8 jsonparser: Add gtk_json_parser_select_string()
Same semantics as select_member(), useful for values where only a known
set of values is accepted, like enumerations encoded as strings.
2023-04-15 06:11:54 +02:00
Benjamin Otte
0dc5caf6b9 jsonparser: Implement integer getters 2023-04-15 06:11:54 +02:00
Benjamin Otte
fa1d01b2b7 demos: Add a simple JSON demo
Download the GTK releases from the gitlab REST API and display them in a
list.

Much creative.
2023-04-15 06:11:54 +02:00
Benjamin Otte
8161c84f76 jsonparser: Add some convenience API
gtk_json_parser_rewind()
Allows to reread the current object/array.

gtk_json_parser_has_member()
Checks the name of the current member.

gtk_json_parser_find_member()
Finds the member with the given name (or forwards to end of document and
returns FALSE);
2023-04-15 06:11:54 +02:00
Benjamin Otte
a147da1120 testsuite: Run json parser as a single test
Much faster.
2023-04-15 06:11:54 +02:00
Benjamin Otte
f953673edb testsuite: Add a testsuite for the json parser
Add a simple test runner and the testsuite from https://github.com/nst/JSONTestSuite
2023-04-15 06:11:54 +02:00
Benjamin Otte
670286eb9e jsonparser: Don't treat \0 as eof
Instead, treat it as regular part of the data and emit errors when we
encounter it.
2023-04-15 06:11:54 +02:00
Benjamin Otte
a7a3ec8a16 jsonparser: Don't stack overflow
Handle skipping deep nested object/array trees when skipping them
without causing stack overflow.

The code is somewhat brittle, but it seems to work.
2023-04-15 06:11:54 +02:00
Benjamin Otte
506b009925 jsonparser: Accept UTF-8 BOM at start of document 2023-04-15 06:11:54 +02:00
Benjamin Otte
1459810b43 jsonparser: Improve error handling
* Add a custom GError domain with our own error values
 * Add API so external code can emit errors
 * Add location information to errors
 * Allow API to query errors
2023-04-15 06:11:54 +02:00
Benjamin Otte
798c35f506 jsonparser: Make select_member() fast
Introduce a JsonStringIterator that allows iterating over a json encoded
string without memory allocation - use that code to do strcmp()-like
matching as well as strdup()-like copying.

Much fancy now.
2023-04-15 06:11:54 +02:00
Benjamin Otte
424e5eaf4e json: Add gtk_json_parser_select_member()
This is modeled after JsonReader.selectName() from the Java Moshi
package, see https://square.github.io/moshi/1.x/moshi/com/squareup/moshi/JsonReader.html#selectName-com.squareup.moshi.JsonReader.Options-

It returns the ID of the current member name in the passed i char array
or -1 if it is not found.

This allows writing code like:
  do {
    switch (gtk_json_parser_select_member (parser, { "foo", "bar", NULL })
    {
    case 0:
      /* handle the "foo" member */
      break;
    case 1:
      /* handle the "bar" member */
      break;
    default:
      /* do nothing or complain or whatever */
      break;
    }
  } while (gtk_json_parser_next (parser));

The impementation so far is the simplest one possible, but could be
significantly improved.
2023-04-15 06:11:54 +02:00
Benjamin Otte
9bc458fa8b jsonparser: Store the node in the character table
That turns the get_node() function into a simple character lookup.
2023-04-15 06:11:54 +02:00
Benjamin Otte
353a841ec7 tools: Add gtk-json-validate
It's a trivial port of json-glib-validate.
2023-04-15 06:11:54 +02:00
Benjamin Otte
0c3e5fdcfb jsonparser: Remove the reader object
Put the members directly into the JsonParser object.
2023-04-15 06:11:54 +02:00
Benjamin Otte
eb84b58ee9 jsonparser: Prevalidate numbers and strings
Validation and parsing are 2 separate steps anyway.
2023-04-15 06:11:54 +02:00
Benjamin Otte
37ccc972bf jsonparser: Split parsing strings from unescaping them 2023-04-15 06:11:54 +02:00
Benjamin Otte
0265714708 jsonparser: Build a character table and use it
Instead of using strchr() and friends, build a table with character
flags, so that we can implement various functions with it.
2023-04-15 06:11:54 +02:00
Benjamin Otte
04348ebf87 jsonparser: Remove line number tracking
We can parse the document manually if we're interested.
2023-04-15 06:11:54 +02:00
Benjamin Otte
489d9398df tools: Add gtk-json-format
The tool is modeled after json-glib-format and supports all the same
arguments.
2023-04-15 06:11:54 +02:00
Benjamin Otte
901a9edeb8 Add GtkJsonPrinter 2023-04-15 06:11:54 +02:00
Benjamin Otte
7ce2d70e0e Add GtkJsonParser 2023-04-15 06:11:54 +02:00
Benjamin Otte
8d0e09c086 testutils: Rewrite to use GSubprocess
That way, we also properly catch all errors and don't let tests pass
when diff(1) fails.

Oops.
2023-04-15 06:11:54 +02:00
551 changed files with 4793 additions and 36 deletions

View File

@@ -192,6 +192,9 @@
<file>listview_minesweeper.ui</file>
<file>listview_minesweeper_cell.ui</file>
</gresource>
<gresource prefix="/listview_releases">
<file>listview_releases.ui</file>
</gresource>
<gresource prefix="/listview_settings">
<file>listview_settings.ui</file>
</gresource>
@@ -311,6 +314,7 @@
<file>listview_filebrowser.c</file>
<file>listview_minesweeper.c</file>
<file>listview_selections.c</file>
<file>listview_releases.c</file>
<file>listview_settings.c</file>
<file>listview_ucd.c</file>
<file>listview_weather.c</file>

View File

@@ -0,0 +1,256 @@
/* Lists/Releases
* #Keywords: GtkListItemFactory, GListModel,JSON
*
* This demo downloads GTK's latest release and displays them in a list.
*
* It shows how hard it still is to get JSON into lists.
*/
#include <gtk/gtk.h>
#include "gtk/json/gtkjsonparserprivate.h"
#define GTK_TYPE_RELEASE (gtk_release_get_type ())
G_DECLARE_FINAL_TYPE (GtkRelease, gtk_release, GTK, RELEASE, GObject)
struct _GtkRelease
{
GObject parent_instance;
char *name;
GDateTime *timestamp;
};
struct _GtkReleaseClass
{
GObjectClass parent_class;
};
enum {
PROP_0,
PROP_NAME,
PROP_TIMESTAMP,
N_PROPS
};
G_DEFINE_TYPE (GtkRelease, gtk_release, G_TYPE_OBJECT)
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_release_finalize (GObject *object)
{
GtkRelease *self = GTK_RELEASE (object);
g_free (self->name);
g_clear_pointer (&self->timestamp, g_date_time_unref);
G_OBJECT_CLASS (gtk_release_parent_class)->finalize (object);
}
static void
gtk_release_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkRelease *self = GTK_RELEASE (object);
switch (property_id)
{
case PROP_NAME:
g_value_set_string (value, self->name);
break;
case PROP_TIMESTAMP:
{
if (self->timestamp)
g_value_take_string (value, g_date_time_format (self->timestamp, "%x"));
else
g_value_set_string (value, "---");
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_release_class_init (GtkReleaseClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = gtk_release_finalize;
gobject_class->get_property = gtk_release_get_property;
properties[PROP_NAME] =
g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_TIMESTAMP] =
g_param_spec_string ("timestamp", NULL, NULL, NULL, G_PARAM_READABLE);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
gtk_release_init (GtkRelease *self)
{
}
static GtkRelease *
gtk_release_new (const char *name,
GDateTime *timestamp)
{
GtkRelease *result;
result = g_object_new (GTK_TYPE_RELEASE, NULL);
result->name = g_strdup (name);
if (timestamp)
result->timestamp = g_date_time_ref (timestamp);
return result;
}
static void
loaded_some_releases_cb (GObject *file,
GAsyncResult *res,
gpointer userdata)
{
GListStore *store = userdata;
GBytes *bytes;
GtkJsonParser *parser;
GError *error = NULL;
bytes = g_file_load_bytes_finish (G_FILE (file), res, NULL, &error);
if (bytes == NULL)
{
g_printerr ("Error loading: %s\n", error->message);
g_clear_error (&error);
return;
}
parser = gtk_json_parser_new_for_bytes (bytes);
g_bytes_unref (bytes);
gtk_json_parser_start_array (parser);
do
{
enum { NAME, COMMIT };
static const char *options[] = { "name", "commit", NULL };
GDateTime *created = NULL;
char *name = NULL;
gtk_json_parser_start_object (parser);
do
{
switch (gtk_json_parser_select_member (parser, options))
{
case NAME:
g_clear_pointer (&name, g_free);
name = gtk_json_parser_get_string (parser);
break;
case COMMIT:
g_clear_pointer (&created, g_date_time_unref);
gtk_json_parser_start_object (parser);
if (gtk_json_parser_find_member (parser, "created_at"))
{
char *created_string = gtk_json_parser_get_string (parser);
created = g_date_time_new_from_iso8601 (created_string, NULL);
g_free (created_string);
}
gtk_json_parser_end (parser);
break;
default:
break;
}
}
while (gtk_json_parser_next (parser));
gtk_json_parser_end (parser);
if (name)
{
GtkRelease *release = gtk_release_new (name, created);
g_list_store_append (store, release);
g_object_unref (release);
}
g_clear_pointer (&name, g_free);
g_clear_pointer (&created, g_date_time_unref);
}
while (gtk_json_parser_next (parser));
gtk_json_parser_end (parser);
if (gtk_json_parser_get_error (parser))
{
const GError *json_error = gtk_json_parser_get_error (parser);
g_printerr ("Error parsing: %s\n", json_error->message);
}
gtk_json_parser_free (parser);
gtk_toggle_button_set_active (g_object_get_data (G_OBJECT (store), "togglebutton"), FALSE);
}
static void
load_some_releases (GListStore *store)
{
GFile *file;
guint n_items;
char *url;
n_items = g_list_model_get_n_items (G_LIST_MODEL (store));
if (n_items == 0)
url = g_strdup ("https://gitlab.gnome.org/api/v4/projects/665/repository/tags");
else
url = g_strdup_printf ("https://gitlab.gnome.org/api/v4/projects/665/repository/tags?page=%u", (n_items + 39) / 20);
file = g_file_new_for_uri (url);
g_file_load_bytes_async (file, NULL, loaded_some_releases_cb, store);
g_object_unref (file);
g_free (url);
}
static GtkWidget *window = NULL;
GtkWidget *
do_listview_releases (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkBuilderScope *scope;
GtkBuilder *builder;
GListStore *list;
GtkWidget *more_button;
g_type_ensure (GTK_TYPE_RELEASE);
scope = gtk_builder_cscope_new ();
gtk_builder_cscope_add_callback_symbol (GTK_BUILDER_CSCOPE (scope), "load_some_releases", (GCallback)load_some_releases);
builder = gtk_builder_new ();
gtk_builder_set_scope (builder, scope);
g_object_unref (scope);
gtk_builder_add_from_resource (builder, "/listview_releases/listview_releases.ui", NULL);
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
more_button = GTK_WIDGET (gtk_builder_get_object (builder, "more_button"));
list = G_LIST_STORE (gtk_builder_get_object (builder, "list"));
g_object_set_data (G_OBJECT (list), "togglebutton", more_button);
load_some_releases (list);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (more_button), TRUE);
}
if (!gtk_widget_get_visible (window))
gtk_widget_set_visible (window, TRUE);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GListStore" id="list">
<property name="item-type">GtkRelease</property>
</object>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Releases</property>
<property name="default-width">640</property>
<property name="default-height">480</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<child type="start">
<object class="GtkToggleButton" id="more_button">
<property name="label">More…</property>
<property name="sensitive" bind-source="more_button" bind-property="active" bind-flags="invert-boolean" />
<signal name="clicked" handler="load_some_releases" object="list" swapped="yes"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<child>
<object class="GtkColumnView" id="columnview">
<property name="model">
<object class="GtkSingleSelection">
<property name="model">list</property>
</object>
</property>
<style>
<class name="data-table"/>
</style>
<child>
<object class="GtkColumnViewColumn" id="name_column">
<property name="title">Name</property>
<property name="resizable">1</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="name" type="GtkRelease">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn">
<property name="title">Date</property>
<property name="resizable">1</property>
<property name="expand">1</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="timestamp" type="GtkRelease">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -56,6 +56,7 @@ demos = files([
'listview_filebrowser.c',
'listview_minesweeper.c',
'listview_selections.c',
'listview_releases.c',
'listview_settings.c',
'listview_ucd.c',
'listview_weather.c',
@@ -103,7 +104,7 @@ demos = files([
'font_features.c',
])
gtkdemo_deps = [ libgtk_dep, ]
gtkdemo_deps = [ libgtk_dep, libgtk_json_dep ]
extra_demo_sources = files([
'main.c',
@@ -248,6 +249,7 @@ executable('gtk4-demo',
include_directories: confinc,
win_subsystem: 'windows',
link_args: extra_demo_ldflags,
link_with: [ libgtk_json, ],
install: true,
)

1735
gtk/json/gtkjsonparser.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_JSON_PARSER_H__
#define __GTK_JSON_PARSER_H__
#include <glib.h>
G_BEGIN_DECLS
typedef enum {
GTK_JSON_NONE,
GTK_JSON_NULL,
GTK_JSON_BOOLEAN,
GTK_JSON_NUMBER,
GTK_JSON_STRING,
GTK_JSON_OBJECT,
GTK_JSON_ARRAY
} GtkJsonNode;
typedef enum {
GTK_JSON_ERROR_FAILED,
GTK_JSON_ERROR_SYNTAX,
GTK_JSON_ERROR_TYPE,
GTK_JSON_ERROR_VALUE,
GTK_JSON_ERROR_SCHEMA,
} GtkJsonError;
typedef struct _GtkJsonParser GtkJsonParser;
#define GTK_JSON_ERROR (gtk_json_error_quark ())
GQuark gtk_json_error_quark (void);
GtkJsonParser * gtk_json_parser_new_for_bytes (GBytes *bytes);
GtkJsonParser * gtk_json_parser_new_for_string (const char *string,
gssize size);
void gtk_json_parser_free (GtkJsonParser *self);
gboolean gtk_json_parser_next (GtkJsonParser *self);
void gtk_json_parser_rewind (GtkJsonParser *self);
gsize gtk_json_parser_get_depth (GtkJsonParser *self);
GtkJsonNode gtk_json_parser_get_node (GtkJsonParser *self);
char * gtk_json_parser_get_member_name (GtkJsonParser *self);
gboolean gtk_json_parser_has_member (GtkJsonParser *self,
const char *name);
gboolean gtk_json_parser_find_member (GtkJsonParser *self,
const char *name);
gssize gtk_json_parser_select_member (GtkJsonParser *self,
const char * const *options);
gboolean gtk_json_parser_get_boolean (GtkJsonParser *self);
double gtk_json_parser_get_number (GtkJsonParser *self);
int gtk_json_parser_get_int (GtkJsonParser *self);
guint gtk_json_parser_get_uint (GtkJsonParser *self);
char * gtk_json_parser_get_string (GtkJsonParser *self);
gssize gtk_json_parser_select_string (GtkJsonParser *self,
const char * const *options);
gboolean gtk_json_parser_start_object (GtkJsonParser *self);
gboolean gtk_json_parser_start_array (GtkJsonParser *self);
gboolean gtk_json_parser_end (GtkJsonParser *self);
const GError * gtk_json_parser_get_error (GtkJsonParser *self) G_GNUC_PURE;
void gtk_json_parser_get_error_offset (GtkJsonParser *self,
gsize *start,
gsize *end);
void gtk_json_parser_get_error_location (GtkJsonParser *self,
gsize *start_line,
gsize *start_line_bytes,
gsize *end_line,
gsize *end_line_bytes);
void gtk_json_parser_value_error (GtkJsonParser *self,
const char *format,
...) G_GNUC_PRINTF(2, 3);
void gtk_json_parser_schema_error (GtkJsonParser *self,
const char *format,
...) G_GNUC_PRINTF(2, 3);
G_END_DECLS
#endif /* __GTK_JSON_PARSER_H__ */

390
gtk/json/gtkjsonprinter.c Normal file
View File

@@ -0,0 +1,390 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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_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);
}

View File

@@ -0,0 +1,76 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_JSON_PRINTER_H__
#define __GTK_JSON_PRINTER_H__
#include <glib.h>
G_BEGIN_DECLS
typedef struct _GtkJsonPrinter GtkJsonPrinter;
typedef enum {
GTK_JSON_PRINTER_PRETTY = (1 << 0),
GTK_JSON_PRINTER_ASCII = (1 << 1),
} GtkJsonPrinterFlags;
typedef void (* GtkJsonPrinterWriteFunc) (GtkJsonPrinter *printer,
const char *s,
gpointer user_data);
GtkJsonPrinter * gtk_json_printer_new (GtkJsonPrinterWriteFunc write_func,
gpointer data,
GDestroyNotify destroy);
void gtk_json_printer_free (GtkJsonPrinter *self);
void gtk_json_printer_set_flags (GtkJsonPrinter *self,
GtkJsonPrinterFlags flags);
GtkJsonPrinterFlags gtk_json_printer_get_flags (GtkJsonPrinter *self);
void gtk_json_printer_set_indentation (GtkJsonPrinter *self,
gsize amount);
gsize gtk_json_printer_get_indentation (GtkJsonPrinter *self);
gsize gtk_json_printer_get_depth (GtkJsonPrinter *self);
gsize gtk_json_printer_get_n_elements (GtkJsonPrinter *self);
void gtk_json_printer_add_boolean (GtkJsonPrinter *self,
const char *name,
gboolean value);
void gtk_json_printer_add_number (GtkJsonPrinter *self,
const char *name,
double value);
void gtk_json_printer_add_string (GtkJsonPrinter *self,
const char *name,
const char *s);
void gtk_json_printer_add_null (GtkJsonPrinter *self,
const char *name);
void gtk_json_printer_start_object (GtkJsonPrinter *self,
const char *name);
void gtk_json_printer_start_array (GtkJsonPrinter *self,
const char *name);
void gtk_json_printer_end (GtkJsonPrinter *self);
G_END_DECLS
#endif /* __GTK_JSON_PRINTER_H__ */

30
gtk/json/meson.build Normal file
View File

@@ -0,0 +1,30 @@
gtk_json_sources = files([
'gtkjsonparser.c',
'gtkjsonprinter.c',
])
gtk_json_deps = [
libm,
glib_dep,
gobject_dep,
platform_gio_dep,
]
libgtk_json = static_library('gtk_json',
sources: gtk_json_sources,
dependencies: gtk_json_deps,
include_directories: [ confinc, ],
c_args: [
'-DGTK_COMPILATION',
'-DG_LOG_DOMAIN="Gtk"',
] + common_cflags,
)
# We don't have link_with: to internal static libs here on purpose, just
# list the dependencies and generated headers and such, for use in the
# "public" libgtk_dep used by internal executables.
libgtk_json_dep = declare_dependency(include_directories: [ confinc, ],
sources: [ ],
link_with: [ libgtk_json ],
dependencies: gtk_json_deps,
)

View File

@@ -747,6 +747,7 @@ build_gir = gir.found() and (get_option('introspection').enabled() or
project_build_root = meson.current_build_dir()
subdir('gtk/css')
subdir('gtk/json')
subdir('gdk')
subdir('gsk')
subdir('gtk')

View File

@@ -0,0 +1 @@
[123.456e-789]

View File

@@ -0,0 +1 @@
[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]

View File

@@ -0,0 +1 @@
[-1e+9999]

View File

@@ -0,0 +1 @@
[1.5e+9999]

View File

@@ -0,0 +1 @@
[-123123e100000]

View File

@@ -0,0 +1 @@
[123123e100000]

View File

@@ -0,0 +1 @@
[123e-10000000]

View File

@@ -0,0 +1 @@
[-123123123123123123123123123123]

View File

@@ -0,0 +1 @@
[100000000000000000000]

View File

@@ -0,0 +1 @@
[-237462374673276894279832749832423479823246327846]

View File

@@ -0,0 +1 @@
1:3-9: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
{"\uDFAA":0}

View File

@@ -0,0 +1 @@
1:3-9: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
["\uDADA"]

View File

@@ -0,0 +1 @@
1:3-15: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
["\uD888\u1234"]

View File

@@ -0,0 +1 @@
1:1: syntax error: Expected a value

View File

@@ -0,0 +1 @@
1:5: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["日ш<E697A5>"]

View File

@@ -0,0 +1 @@
1:3: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["<22><><EFBFBD>"]

View File

@@ -0,0 +1 @@
1:3-9: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
["\uD800\n"]

View File

@@ -0,0 +1 @@
1:3-9: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
["\uDd1ea"]

View File

@@ -0,0 +1 @@
1:3-15: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
["\uD800\uD800\n"]

View File

@@ -0,0 +1 @@
1:3-9: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
["\ud800"]

View File

@@ -0,0 +1 @@
1:3-9: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
["\ud800abc"]

View File

@@ -0,0 +1 @@
1:3: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["<22>"]

View File

@@ -0,0 +1 @@
1:3-15: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
["\uDd1e\uD834"]

View File

@@ -0,0 +1 @@
1:3: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["<22>"]

View File

@@ -0,0 +1 @@
1:3-9: syntax error: Invalid UTF-16 surrogate pair

View File

@@ -0,0 +1 @@
["\uDFAA"]

View File

@@ -0,0 +1 @@
1:3: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["<22>"]

View File

@@ -0,0 +1 @@
1:3: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["<22><><EFBFBD><EFBFBD>"]

View File

@@ -0,0 +1 @@
1:3: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["<22><>"]

View File

@@ -0,0 +1 @@
1:3: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"]

View File

@@ -0,0 +1 @@
1:3: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"]

View File

@@ -0,0 +1 @@
1:3: syntax error: Invalid UTF-8

View File

@@ -0,0 +1 @@
["<22><>"]

View File

@@ -0,0 +1 @@
1:1: syntax error: Unexpected nul byte in document

View File

@@ -0,0 +1 @@
1:2: syntax error: Unexpected nul byte in document

View File

@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
1:4-8: syntax error: Expected a ',' to separate array members

View File

@@ -0,0 +1 @@
[1 true]

View File

@@ -0,0 +1 @@
1:2-3: syntax error: Expected a value

View File

@@ -0,0 +1 @@
[a<EFBFBD>]

View File

@@ -0,0 +1 @@
1:4-5: syntax error: Expected a ',' to separate array members

View File

@@ -0,0 +1 @@
["": 1]

View File

@@ -0,0 +1 @@
1:5-6: syntax error: Data at end of document

View File

@@ -0,0 +1 @@
[""],

View File

@@ -0,0 +1 @@
1:2-3: syntax error: Expected a value

View File

@@ -0,0 +1 @@
[,1]

View File

@@ -0,0 +1 @@
1:4-5: syntax error: Expected a value

View File

@@ -0,0 +1 @@
[1,,2]

View File

@@ -0,0 +1 @@
1:6-7: syntax error: Expected a value

View File

@@ -0,0 +1 @@
["x",,]

View File

@@ -0,0 +1 @@
1:6-7: syntax error: Data at end of document

View File

@@ -0,0 +1 @@
["x"]]

View File

@@ -0,0 +1 @@
1:5-6: syntax error: Expected a value

View File

@@ -0,0 +1 @@
["",]

View File

@@ -0,0 +1 @@
1:1-5: syntax error: Unterminated array

View File

@@ -0,0 +1 @@
["x"

View File

@@ -0,0 +1 @@
1:2-3: syntax error: Expected a value

View File

@@ -0,0 +1 @@
[x

View File

@@ -0,0 +1 @@
1:3-4: syntax error: Expected a ',' to separate array members

View File

@@ -0,0 +1 @@
[3[4]]

View File

@@ -0,0 +1 @@
1:2: syntax error: Expected a value

View File

@@ -0,0 +1 @@
[<EFBFBD>]

View File

@@ -0,0 +1 @@
1:3-4: syntax error: Expected a ',' to separate array members

View File

@@ -0,0 +1 @@
[1:2]

View File

@@ -0,0 +1 @@
1:2-3: syntax error: Expected a value

View File

@@ -0,0 +1 @@
[,]

View File

@@ -0,0 +1 @@
1:2-3: syntax error: Expected a number after '-' character

View File

@@ -0,0 +1 @@
[-]

Some files were not shown because too many files have changed in this diff Show More