gtk2/gtk/gtkbuilderprecompile.c
Florian Müllner 2715b3ec31 buildable: Make vfunc accessor functions private
With the exception of gtk_buildable_get_id(), those are only used
to construct objects from XML descriptions, which is functionality
internal to GTK.

The API is therefore unlikely to be missed, and keeping it internal
means they can no longer unintentionally shadow object methods in
bindings with less namespacing; for example it's currently ambiguous
whether `infoBar.add_child()` refers to gtk_info_bar_add_child() or
gtk_buildable_add_child().

https://gitlab.gnome.org/GNOME/gtk/-/issues/3191
2020-09-26 02:16:57 +02:00

564 lines
15 KiB
C

/* gtkbuilderparser.c
* Copyright (C) 2019 Red Hat,
* Alexander Larsson <alexander.larsson@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gio/gio.h>
#include "gtkbuilderprivate.h"
#include "gtkbuilder.h"
#include "gtkbuildableprivate.h"
/***************************************** Record a GMarkup parser call ***************************/
typedef enum
{
RECORD_TYPE_ELEMENT,
RECORD_TYPE_END_ELEMENT,
RECORD_TYPE_TEXT,
} RecordTreeType;
typedef struct RecordDataTree RecordDataTree;
/* All strings are owned by the string table */
struct RecordDataTree {
RecordDataTree *parent;
RecordTreeType type;
int n_attributes;
const char *data;
const char **attributes;
const char **values;
GList *children;
};
typedef struct {
char *string;
int count;
int offset;
} RecordDataString;
static RecordDataTree *
record_data_tree_new (RecordDataTree *parent, RecordTreeType type, const char *data)
{
RecordDataTree *tree = g_slice_new0 (RecordDataTree);
tree->parent = parent;
tree->type = type;
tree->data = data;
if (parent)
parent->children = g_list_prepend (parent->children, tree);
return tree;
}
static void
record_data_tree_free (RecordDataTree *tree)
{
g_list_free_full (tree->children, (GDestroyNotify)record_data_tree_free);
g_free (tree->attributes);
g_free (tree->values);
g_slice_free (RecordDataTree, tree);
}
static void
record_data_string_free (RecordDataString *s)
{
g_free (s->string);
g_slice_free (RecordDataString, s);
}
static const char *
record_data_string_lookup (GHashTable *strings, const char *str, gssize len)
{
char *copy = NULL;
RecordDataString *s;
if (len >= 0)
{
/* Ensure str is zero terminated */
copy = g_strndup (str, len);
str = copy;
}
s = g_hash_table_lookup (strings, str);
if (s)
{
g_free (copy);
s->count++;
return s->string;
}
s = g_slice_new (RecordDataString);
s->string = copy ? copy : g_strdup (str);
s->count = 1;
g_hash_table_insert (strings, s->string, s);
return s->string;
}
typedef struct {
GHashTable *strings;
RecordDataTree *root;
RecordDataTree *current;
} RecordData;
static void
record_start_element (GMarkupParseContext *context,
const char *element_name,
const char **names,
const char **values,
gpointer user_data,
GError **error)
{
gsize n_attrs = g_strv_length ((char **)names);
RecordData *data = user_data;
RecordDataTree *child;
int i;
child = record_data_tree_new (data->current, RECORD_TYPE_ELEMENT,
record_data_string_lookup (data->strings, element_name, -1));
data->current = child;
child->n_attributes = n_attrs;
child->attributes = g_new (const char *, n_attrs);
child->values = g_new (const char *, n_attrs);
for (i = 0; i < n_attrs; i++)
{
child->attributes[i] = record_data_string_lookup (data->strings, names[i], -1);
child->values[i] = record_data_string_lookup (data->strings, values[i], -1);
}
}
static void
record_end_element (GMarkupParseContext *context,
const char *element_name,
gpointer user_data,
GError **error)
{
RecordData *data = user_data;
data->current = data->current->parent;
}
static void
record_text (GMarkupParseContext *context,
const char *text,
gsize text_len,
gpointer user_data,
GError **error)
{
RecordData *data = user_data;
record_data_tree_new (data->current, RECORD_TYPE_TEXT,
record_data_string_lookup (data->strings, text, text_len));
}
static const GMarkupParser record_parser =
{
record_start_element,
record_end_element,
record_text,
NULL, // passthrough, not stored
NULL, // error, fails immediately
};
static int
compare_string (gconstpointer _a,
gconstpointer _b)
{
const RecordDataString *a = _a;
const RecordDataString *b = _b;
return b->count - a->count;
}
static void
marshal_uint32 (GString *str,
guint32 v)
{
/*
We encode in a variable length format similar to
utf8:
v size byte 1 byte 2 byte 3 byte 4 byte 5
7 bit: 0xxxxxxx
14 bit: 10xxxxxx xxxxxxxx
21 bit: 110xxxxx xxxxxxxx xxxxxxxx
28 bit: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
32 bit: 11110000 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx
*/
if (v < 128)
{
g_string_append_c (str, (guchar)v);
}
else if (v < (1<<14))
{
g_string_append_c (str, (guchar)(v >> 8) | 0x80);
g_string_append_c (str, (guchar)(v & 0xff));
}
else if (v < (1<<21))
{
g_string_append_c (str, (guchar)(v >> 16) | 0xc0);
g_string_append_c (str, (guchar)((v >> 8) & 0xff));
g_string_append_c (str, (guchar)(v & 0xff));
}
else if (v < (1<<28))
{
g_string_append_c (str, (guchar)(v >> 24) | 0xe0);
g_string_append_c (str, (guchar)((v >> 16) & 0xff));
g_string_append_c (str, (guchar)((v >> 8) & 0xff));
g_string_append_c (str, (guchar)(v & 0xff));
}
else
{
g_string_append_c (str, 0xf0);
g_string_append_c (str, (guchar)((v >> 24) & 0xff));
g_string_append_c (str, (guchar)((v >> 16) & 0xff));
g_string_append_c (str, (guchar)((v >> 8) & 0xff));
g_string_append_c (str, (guchar)(v & 0xff));
}
}
static void
marshal_string (GString *marshaled,
GHashTable *strings,
const char *string)
{
RecordDataString *s;
s = g_hash_table_lookup (strings, string);
g_assert (s != NULL);
marshal_uint32 (marshaled, s->offset);
}
static void
marshal_tree (GString *marshaled,
GHashTable *strings,
RecordDataTree *tree)
{
GList *l;
int i;
/* Special case the root */
if (tree->parent == NULL)
{
for (l = g_list_last (tree->children); l != NULL; l = l->prev)
marshal_tree (marshaled, strings, l->data);
return;
}
switch (tree->type)
{
case RECORD_TYPE_ELEMENT:
marshal_uint32 (marshaled, RECORD_TYPE_ELEMENT);
marshal_string (marshaled, strings, tree->data);
marshal_uint32 (marshaled, tree->n_attributes);
for (i = 0; i < tree->n_attributes; i++)
{
marshal_string (marshaled, strings, tree->attributes[i]);
marshal_string (marshaled, strings, tree->values[i]);
}
for (l = g_list_last (tree->children); l != NULL; l = l->prev)
marshal_tree (marshaled, strings, l->data);
marshal_uint32 (marshaled, RECORD_TYPE_END_ELEMENT);
break;
case RECORD_TYPE_TEXT:
marshal_uint32 (marshaled, RECORD_TYPE_TEXT);
marshal_string (marshaled, strings, tree->data);
break;
case RECORD_TYPE_END_ELEMENT:
default:
g_assert_not_reached ();
}
}
/**
* _gtk_buildable_parser_precompile:
* @text: chunk of text to parse
* @text_len: length of @text in bytes
*
* Converts the xml format typically used by GtkBuilder to a
* binary form that is more efficient to parse. This is a custom
* format that is only supported by GtkBuilder.
*
* returns: A #GByte with the precompiled data
**/
GBytes *
_gtk_buildable_parser_precompile (const char *text,
gssize text_len,
GError **error)
{
GMarkupParseContext *ctx;
RecordData data = { 0 };
GList *string_table, *l;
GString *marshaled;
int offset;
data.strings = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)record_data_string_free);
data.root = record_data_tree_new (NULL, RECORD_TYPE_ELEMENT, NULL);
data.current = data.root;
ctx = g_markup_parse_context_new (&record_parser, G_MARKUP_TREAT_CDATA_AS_TEXT,
&data, NULL);
if (!g_markup_parse_context_parse (ctx, text, text_len, error))
{
record_data_tree_free (data.root);
g_hash_table_destroy (data.strings);
g_markup_parse_context_free (ctx);
return NULL;
}
g_markup_parse_context_free (ctx);
string_table = g_hash_table_get_values (data.strings);
string_table = g_list_sort (string_table, compare_string);
offset = 0;
for (l = string_table; l != NULL; l = l->next)
{
RecordDataString *s = l->data;
s->offset = offset;
offset += strlen (s->string) + 1;
}
marshaled = g_string_new ("");
/* Magic marker */
g_string_append_len (marshaled, "GBU\0", 4);
marshal_uint32 (marshaled, offset);
for (l = string_table; l != NULL; l = l->next)
{
RecordDataString *s = l->data;
g_string_append_len (marshaled, s->string, strlen (s->string) + 1);
}
g_list_free (string_table);
marshal_tree (marshaled, data.strings, data.root);
record_data_tree_free (data.root);
g_hash_table_destroy (data.strings);
return g_string_free_to_bytes (marshaled);
}
/***************************************** Replay GMarkup parser callbacks ***************************/
static guint32
demarshal_uint32 (const char **tree)
{
const guchar *p = (const guchar *)*tree;
guchar c = *p;
/* see marshal_uint32 for format */
if (c < 128) /* 7 bit */
{
*tree += 1;
return c;
}
else if ((c & 0xc0) == 0x80) /* 14 bit */
{
*tree += 2;
return (c & 0x3f) << 8 | p[1];
}
else if ((c & 0xe0) == 0xc0) /* 21 bit */
{
*tree += 3;
return (c & 0x1f) << 16 | p[1] << 8 | p[2];
}
else if ((c & 0xf0) == 0xe0) /* 28 bit */
{
*tree += 4;
return (c & 0xf) << 24 | p[1] << 16 | p[2] << 8 | p[3];
}
else
{
*tree += 5;
return p[1] << 24 | p[2] << 16 | p[3] << 8 | p[4];
}
}
static const char *
demarshal_string (const char **tree, const char *strings)
{
guint32 offset = demarshal_uint32 (tree);
return strings + offset;
}
static void
propagate_error (GtkBuildableParseContext *context,
GError **dest,
GError *src)
{
(*context->internal_callbacks->error) (NULL, src, context);
g_propagate_error (dest, src);
}
static gboolean
replay_start_element (GtkBuildableParseContext *context,
const char **tree,
const char *strings,
GError **error)
{
const char *element_name;
guint32 i, n_attrs;
const char **attr_names;
const char **attr_values;
GError *tmp_error = NULL;
element_name = demarshal_string (tree, strings);
n_attrs = demarshal_uint32 (tree);
attr_names = g_newa (const char *, n_attrs + 1);
attr_values = g_newa (const char *, n_attrs + 1);
for (i = 0; i < n_attrs; i++)
{
attr_names[i] = demarshal_string (tree, strings);
attr_values[i] = demarshal_string (tree, strings);
}
attr_names[i] = NULL;
attr_values[i] = NULL;
(* context->internal_callbacks->start_element) (NULL,
element_name,
attr_names,
attr_values,
context,
&tmp_error);
if (tmp_error)
{
propagate_error (context, error, tmp_error);
return FALSE;
}
return TRUE;
}
static gboolean
replay_end_element (GtkBuildableParseContext *context,
const char **tree,
const char *strings,
GError **error)
{
GError *tmp_error = NULL;
(* context->internal_callbacks->end_element) (NULL,
gtk_buildable_parse_context_get_element (context),
context,
&tmp_error);
if (tmp_error)
{
propagate_error (context, error, tmp_error);
return FALSE;
}
return TRUE;
}
static gboolean
replay_text (GtkBuildableParseContext *context,
const char **tree,
const char *strings,
GError **error)
{
const char *text;
GError *tmp_error = NULL;
text = demarshal_string (tree, strings);
(*context->internal_callbacks->text) (NULL,
text,
strlen (text),
context,
&tmp_error);
if (tmp_error)
{
propagate_error (context, error, tmp_error);
return FALSE;
}
return TRUE;
}
gboolean
_gtk_buildable_parser_is_precompiled (const char *data,
gssize data_len)
{
return
data_len > 4 &&
data[0] == 'G' &&
data[1] == 'B' &&
data[2] == 'U' &&
data[3] == 0;
}
gboolean
_gtk_buildable_parser_replay_precompiled (GtkBuildableParseContext *context,
const char *data,
gssize data_len,
GError **error)
{
const char *data_end = data + data_len;
guint32 type, len;
const char *strings;
const char *tree;
data = data + 4; /* Skip header */
len = demarshal_uint32 (&data);
strings = data;
data = data + len;
tree = data;
while (tree < data_end)
{
gboolean res;
type = demarshal_uint32 (&tree);
switch (type)
{
case RECORD_TYPE_ELEMENT:
res = replay_start_element (context, &tree, strings, error);
break;
case RECORD_TYPE_END_ELEMENT:
res = replay_end_element (context, &tree, strings, error);
break;
case RECORD_TYPE_TEXT:
res = replay_text (context, &tree, strings, error);
break;
default:
g_assert_not_reached ();
}
if (!res)
return FALSE;
}
return TRUE;
}