gtk2/gtk/tools/gtk-builder-tool-simplify.c
2020-07-25 00:47:36 +02:00

1981 lines
53 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Copyright 2015 Red Hat, Inc.
*
* GTK+ 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 of the
* License, or (at your option) any later version.
*
* GLib 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 GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib/gi18n.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "gtkbuilderprivate.h"
#include "gtk-builder-tool.h"
typedef struct Element Element;
struct Element {
Element *parent;
char *element_name;
char **attribute_names;
char **attribute_values;
char *data;
GList *children;
int line_number;
int char_number;
};
static void
free_element (gpointer data)
{
Element *element = data;
g_list_free_full (element->children, free_element);
g_free (element->element_name);
g_strfreev (element->attribute_names);
g_strfreev (element->attribute_values);
g_free (element->data);
g_free (element);
}
typedef struct {
Element *root;
Element *current;
GString *value;
GtkBuilder *builder;
const char *input_filename;
char *output_filename;
FILE *output;
gboolean convert3to4;
} MyParserData;
static void
start_element (GMarkupParseContext *context,
const char *element_name,
const char **attribute_names,
const char **attribute_values,
gpointer user_data,
GError **error)
{
MyParserData *data = user_data;
Element *elt;
elt = g_new0 (Element, 1);
elt->parent = data->current;
elt->element_name = g_strdup (element_name);
elt->attribute_names = g_strdupv ((char **)attribute_names);
elt->attribute_values = g_strdupv ((char **)attribute_values);
g_markup_parse_context_get_position (context, &elt->line_number, &elt->char_number);
if (data->current)
data->current->children = g_list_append (data->current->children, elt);
data->current = elt;
if (data->root == NULL)
data->root = elt;
g_string_truncate (data->value, 0);
}
static void
end_element (GMarkupParseContext *context,
const char *element_name,
gpointer user_data,
GError **error)
{
MyParserData *data = user_data;
data->current->data = g_strdup (data->value->str);
data->current = data->current->parent;
}
static void
text (GMarkupParseContext *context,
const char *text,
gsize text_len,
gpointer user_data,
GError **error)
{
MyParserData *data = user_data;
if (data->value)
{
g_string_append_len (data->value, text, text_len);
return;
}
}
static GMarkupParser parser = {
start_element,
end_element,
text,
NULL,
NULL
};
static const char *
canonical_boolean_value (MyParserData *data,
const char *string)
{
GValue value = G_VALUE_INIT;
gboolean b = FALSE;
if (gtk_builder_value_from_string_type (data->builder, G_TYPE_BOOLEAN, string, &value, NULL))
b = g_value_get_boolean (&value);
return b ? "1" : "0";
}
typedef enum {
PROP_KIND_OBJECT,
PROP_KIND_PACKING,
PROP_KIND_CELL_PACKING,
PROP_KIND_LAYOUT
} PropKind;
static PropKind
get_prop_kind (Element *element)
{
g_assert (g_str_equal (element->element_name, "property"));
if (g_str_equal (element->parent->element_name, "packing"))
return PROP_KIND_PACKING;
else if (g_str_equal (element->parent->element_name, "layout"))
return PROP_KIND_LAYOUT;
else if (g_str_equal (element->parent->element_name, "cell-packing"))
return PROP_KIND_CELL_PACKING;
else
return PROP_KIND_OBJECT;
}
/* A number of properties unfortunately can't be omitted even
* if they are nominally set to their default value. In many
* cases, this is due to subclasses not overriding the default
* value from the superclass.
*/
static gboolean
needs_explicit_setting (GParamSpec *pspec,
PropKind kind)
{
struct _Prop {
const char *class;
const char *property;
PropKind kind;
} props[] = {
{ "GtkAboutDialog", "program-name", PROP_KIND_OBJECT },
{ "GtkCalendar", "year", PROP_KIND_OBJECT },
{ "GtkCalendar", "month", PROP_KIND_OBJECT },
{ "GtkCalendar", "day", PROP_KIND_OBJECT },
{ "GtkPlacesSidebar", "show-desktop", PROP_KIND_OBJECT },
{ "GtkRadioButton", "draw-indicator", PROP_KIND_OBJECT },
{ "GtkWidget", "hexpand", PROP_KIND_OBJECT },
{ "GtkWidget", "vexpand", PROP_KIND_OBJECT },
{ "GtkGridLayoutChild", "top-attach", PROP_KIND_LAYOUT },
{ "GtkGridLayoutChild", "left-attach", PROP_KIND_LAYOUT },
};
gboolean found;
int k;
const char *class_name;
class_name = g_type_name (pspec->owner_type);
found = FALSE;
for (k = 0; k < G_N_ELEMENTS (props); k++)
{
if (strcmp (class_name, props[k].class) == 0 &&
strcmp (pspec->name, props[k].property) == 0 &&
kind == props[k].kind)
{
found = TRUE;
break;
}
}
return found;
}
static gboolean
keep_for_rewrite (const char *class_name,
const char *property_name,
PropKind kind)
{
struct _Prop {
const char *class;
const char *property;
PropKind kind;
} props[] = {
{ "GtkPopover", "modal", PROP_KIND_OBJECT },
{ "GtkActionBar", "pack-type", PROP_KIND_PACKING },
{ "GtkHeaderBar", "pack-type", PROP_KIND_PACKING },
{ "GtkPopoverMenu", "submenu", PROP_KIND_PACKING },
{ "GtkToolbar", "expand", PROP_KIND_PACKING },
{ "GtkToolbar", "homogeneous", PROP_KIND_PACKING },
{ "GtkPaned", "resize", PROP_KIND_PACKING },
{ "GtkPaned", "shrink", PROP_KIND_PACKING },
{ "GtkOverlay", "measure", PROP_KIND_PACKING },
{ "GtkOverlay", "clip-overlay", PROP_KIND_PACKING },
{ "GtkGrid", "left-attach", PROP_KIND_PACKING },
{ "GtkGrid", "top-attach", PROP_KIND_PACKING },
{ "GtkGrid", "width", PROP_KIND_PACKING },
{ "GtkGrid", "height", PROP_KIND_PACKING },
{ "GtkStack", "name", PROP_KIND_PACKING },
{ "GtkStack", "title", PROP_KIND_PACKING },
{ "GtkStack", "icon-name", PROP_KIND_PACKING },
{ "GtkStack", "needs-attention", PROP_KIND_PACKING },
};
gboolean found;
int k;
char *canonical_name;
canonical_name = g_strdup (property_name);
g_strdelimit (canonical_name, "_", '-');
found = FALSE;
for (k = 0; k < G_N_ELEMENTS (props); k++)
{
if (strcmp (class_name, props[k].class) == 0 &&
strcmp (canonical_name, props[k].property) == 0 &&
kind == props[k].kind)
{
found = TRUE;
break;
}
}
g_free (canonical_name);
return found;
}
static gboolean
has_attribute (Element *elt,
const char *name,
const char *value)
{
int i;
for (i = 0; elt->attribute_names[i]; i++)
{
if (strcmp (elt->attribute_names[i], name) == 0 &&
(value == NULL || strcmp (elt->attribute_values[i], value) == 0))
return TRUE;
}
return FALSE;
}
static gboolean
is_cdata_property (Element *element)
{
if (g_str_equal (element->element_name, "property") &&
has_attribute (element, "name", "bytes") &&
element->parent != NULL &&
g_str_equal (element->parent->element_name, "object") &&
has_attribute (element->parent, "class", "GtkBuilderListItemFactory"))
return TRUE;
return FALSE;
}
static gboolean
is_pcdata_element (Element *element)
{
/* elements that can contain text */
const char *names[] = {
"property",
"attribute",
"action-widget",
"pattern",
"mime-type",
"col",
"item",
"mark",
NULL,
};
if (g_str_equal (element->element_name, "property") &&
(g_strv_contains ((const char * const *)element->attribute_names, "bind-source") ||
g_strv_contains ((const char * const *)element->attribute_names, "bind_source")))
return FALSE;
if (g_strv_contains (names, element->element_name))
return TRUE;
return FALSE;
}
static gboolean
is_container_element (Element *element)
{
/* elements that just hold a list of things and
* can be omitted when they have no children
*/
const char *names[] = {
"packing",
"layout",
"cell-packing",
"attributes",
"action-widgets",
"patterns",
"mime-types",
"attributes",
"row",
"items",
NULL
};
if (g_strv_contains (names, element->element_name))
return TRUE;
return FALSE;
}
static void
canonicalize_key (char *key)
{
char *p;
for (p = key; *p != 0; p++)
{
char c = *p;
/* We may meet something like AtkObject::accessible-name */
if (c == ':' && ((p > key && p[-1] == ':') || p[1] == ':'))
continue;
if (c != '-' &&
(c < '0' || c > '9') &&
(c < 'A' || c > 'Z') &&
(c < 'a' || c > 'z'))
*p = '-';
}
}
static struct {
const char *class;
const char *layout_manager;
} layout_managers[] = {
{ "GtkBox", "GtkBoxLayout" },
{ "GtkGrid", "GtkGridLayout" },
{ "GtkFixed", "GtkFixedLayout" },
{ "GtkFileChooserButton", "GtkBinLayout" },
{ "GtkFileChooserWidget", "GtkBinLayout" },
{ "GtkOverlay", "GtkOverlayLayout" }
};
static GParamSpec *
get_property_pspec (MyParserData *data,
const char *class_name,
const char *property_name,
PropKind kind)
{
GType type;
GObjectClass *class;
GParamSpec *pspec;
char *canonical_name;
type = g_type_from_name (class_name);
if (type == G_TYPE_INVALID)
{
type = gtk_builder_get_type_from_name (data->builder, class_name);
if (type == G_TYPE_INVALID)
return NULL;
}
class = g_type_class_ref (type);
canonical_name = g_strdup (property_name);
canonicalize_key (canonical_name);
switch (kind)
{
case PROP_KIND_OBJECT:
pspec = g_object_class_find_property (class, canonical_name);
break;
case PROP_KIND_PACKING:
pspec = NULL;
break;
case PROP_KIND_CELL_PACKING:
{
GObjectClass *cell_class;
/* We're just assuming that the cell layout is using a GtkCellAreaBox. */
cell_class = g_type_class_ref (GTK_TYPE_CELL_AREA_BOX);
pspec = gtk_cell_area_class_find_cell_property (GTK_CELL_AREA_CLASS (cell_class), canonical_name);
g_type_class_unref (cell_class);
}
break;
case PROP_KIND_LAYOUT:
{
int i;
const char *layout_manager = NULL;
pspec = NULL;
for (i = 0; i < G_N_ELEMENTS (layout_managers); i++)
{
if (g_str_equal (layout_managers[i].class, class_name))
{
layout_manager = layout_managers[i].layout_manager;
break;
}
}
if (layout_manager)
{
GtkLayoutManagerClass *layout_manager_class;
layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (g_type_class_ref (g_type_from_name (layout_manager)));
if (layout_manager_class->layout_child_type != G_TYPE_INVALID)
{
GObjectClass *layout_child_class;
layout_child_class = g_type_class_ref (layout_manager_class->layout_child_type);
pspec = g_object_class_find_property (layout_child_class, canonical_name);
g_type_class_unref (layout_child_class);
}
g_type_class_unref (layout_manager_class);
}
}
break;
default:
g_assert_not_reached ();
}
g_free (canonical_name);
g_type_class_unref (class);
return pspec;
}
static const char *get_class_name (Element *element);
static gboolean
value_is_default (Element *element,
MyParserData *data,
GParamSpec *pspec,
const char *value_string)
{
GValue value = { 0, };
gboolean ret;
GError *error = NULL;
if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_OBJECT))
return FALSE;
if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_BOXED))
return FALSE;
if (!gtk_builder_value_from_string (data->builder, pspec, value_string, &value, &error))
{
g_printerr (_("%s:%d: Couldnt parse value for property '%s': %s\n"), data->input_filename, element->line_number, pspec->name, error->message);
g_error_free (error);
ret = FALSE;
}
else
{
/* GtkWidget::visible has a 'smart' default */
if (pspec->owner_type == GTK_TYPE_WIDGET &&
g_str_equal (pspec->name, "visible"))
{
const char *class_name = get_class_name (element);
GType type = g_type_from_name (class_name);
gboolean default_value;
if (g_type_is_a (type, GTK_TYPE_ROOT) ||
g_type_is_a (type, GTK_TYPE_POPOVER))
default_value = FALSE;
else
default_value = TRUE;
ret = g_value_get_boolean (&value) == default_value;
}
else
ret = g_param_value_defaults (pspec, &value);
}
g_value_reset (&value);
return ret;
}
static const char *
get_attribute_value (Element *element,
const char *name)
{
int i;
for (i = 0; element->attribute_names[i]; i++)
{
if (g_str_equal (element->attribute_names[i], name))
return element->attribute_values[i];
}
return NULL;
}
static void
set_attribute_value (Element *element,
const char *name,
const char *value)
{
int i;
for (i = 0; element->attribute_names[i]; i++)
{
if (g_str_equal (element->attribute_names[i], name))
{
g_free (element->attribute_values[i]);
element->attribute_values[i] = g_strdup (value);
return;
}
}
}
static gboolean
element_is_object_or_template (Element *element)
{
return g_str_equal (element->element_name, "object") ||
g_str_equal (element->element_name, "template");
}
static const char *
get_class_name (Element *element)
{
Element *parent = element->parent;
if (element_is_object_or_template (element))
parent = element;
if (g_str_equal (parent->element_name, "packing"))
parent = parent->parent->parent; /* child - object */
else if (g_str_equal (parent->element_name, "layout"))
parent = parent->parent->parent->parent; /* object - child - object */
if (g_str_equal (parent->element_name, "object"))
{
return get_attribute_value (parent, "class");
}
else if (g_str_equal (parent->element_name, "template"))
{
if (get_attribute_value (parent, "parent"))
return get_attribute_value (parent, "parent");
else
return get_attribute_value (parent, "class");
}
return NULL;
}
static gboolean
property_is_boolean (Element *element,
MyParserData *data)
{
GParamSpec *pspec;
const char *class_name;
const char *property_name;
int i;
PropKind kind;
kind = get_prop_kind (element);
class_name = get_class_name (element);
property_name = "";
for (i = 0; element->attribute_names[i]; i++)
{
if (strcmp (element->attribute_names[i], "name") == 0)
property_name = (const char *)element->attribute_values[i];
}
pspec = get_property_pspec (data, class_name, property_name, kind);
if (pspec)
return G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN;
return FALSE;
}
static void
warn_missing_property (Element *element,
MyParserData *data,
const char *class_name,
const char *property_name,
PropKind kind)
{
const char *kind_str[] = { "", "Packing ", "Cell ", "Layout " };
g_printerr (_("%s:%d: %sproperty %s::%s not found\n"),
data->input_filename, element->line_number, kind_str[kind], class_name, property_name);
}
static gboolean
property_can_be_omitted (Element *element,
MyParserData *data)
{
int i;
gboolean bound;
gboolean translatable;
const char *class_name;
const char *property_name;
const char *value_string;
GParamSpec *pspec;
PropKind kind;
kind = get_prop_kind (element);
class_name = get_class_name (element);
property_name = "";
value_string = element->data;
bound = FALSE;
translatable = FALSE;
for (i = 0; element->attribute_names[i]; i++)
{
if (strcmp (element->attribute_names[i], "bind-source") == 0 ||
strcmp (element->attribute_names[i], "bind_source") == 0)
bound = TRUE;
else if (strcmp (element->attribute_names[i], "translatable") == 0)
translatable = TRUE;
else if (strcmp (element->attribute_names[i], "name") == 0)
property_name = (const char *)element->attribute_values[i];
}
if (data->convert3to4 &&
keep_for_rewrite (class_name, property_name, kind))
return FALSE; /* keep, will be rewritten */
if (translatable)
return FALSE;
if (bound)
return FALSE;
pspec = get_property_pspec (data, class_name, property_name, kind);
if (pspec == NULL)
{
warn_missing_property (element, data, class_name, property_name, kind);
return FALSE;
}
if (needs_explicit_setting (pspec, kind))
return FALSE;
return value_is_default (element, data, pspec, value_string);
}
static gboolean
property_has_been_removed (Element *element,
MyParserData *data)
{
const char *class_name;
const char *property_name;
struct _Prop {
const char *class;
const char *property;
PropKind kind;
} props[] = {
{ "GtkActionBar", "position", PROP_KIND_PACKING },
{ "GtkButtonBox", "secondary", PROP_KIND_PACKING },
{ "GtkButtonBox", "non-homogeneous", PROP_KIND_PACKING },
{ "GtkBox", "position", PROP_KIND_PACKING },
{ "GtkBox", "pack-type", PROP_KIND_PACKING },
{ "GtkHeaderBar", "position", PROP_KIND_PACKING },
{ "GtkPopoverMenu", "position",PROP_KIND_PACKING },
};
char *canonical_name;
gboolean found;
int i, k;
PropKind kind;
kind = get_prop_kind (element);
class_name = get_class_name (element);
property_name = "";
for (i = 0; element->attribute_names[i]; i++)
{
if (strcmp (element->attribute_names[i], "name") == 0)
property_name = (const char *)element->attribute_values[i];
}
canonical_name = g_strdup (property_name);
g_strdelimit (canonical_name, "_", '-');
found = FALSE;
for (k = 0; k < G_N_ELEMENTS (props); k++)
{
if (strcmp (class_name, props[k].class) == 0 &&
strcmp (canonical_name, props[k].property) == 0 &&
kind == props[k].kind)
{
found = TRUE;
break;
}
}
g_free (canonical_name);
return found;
}
static void
maybe_rename_property (Element *element, MyParserData *data)
{
const char *class_name;
const char *property_name;
struct _Prop {
const char *class;
const char *property;
GType type;
PropKind kind;
const char *new_name;
const char *alt_names[3];
} props[] = {
/* the "replacement" property is placed *after* the "added" properties */
{ "GtkPopover", "modal", GTK_TYPE_POPOVER, PROP_KIND_OBJECT, "autohide", { NULL, NULL, NULL } },
{ "GtkWidget", "expand", GTK_TYPE_WIDGET, PROP_KIND_OBJECT, "vexpand", { "hexpand", NULL, NULL } },
{ "GtkWidget", "margin", GTK_TYPE_WIDGET, PROP_KIND_OBJECT, "margin-bottom", { "margin-start", "margin-end", "margin-top" } },
{ "GtkWidget", "margin-left", GTK_TYPE_WIDGET, PROP_KIND_OBJECT, "margin-start", { NULL, NULL, NULL } },
{ "GtkWidget", "margin-right", GTK_TYPE_WIDGET, PROP_KIND_OBJECT, "margin-end", { NULL, NULL, NULL } },
{ "GtkHeaderBar", "show-close-button", GTK_TYPE_HEADER_BAR, PROP_KIND_OBJECT, "show-title-buttons", { NULL, NULL, NULL } },
{ "GtkHeaderBar", "custom-title", GTK_TYPE_HEADER_BAR, PROP_KIND_OBJECT, "title-widget", { NULL, NULL, NULL } },
{ "GtkStack", "homogeneous", GTK_TYPE_STACK, PROP_KIND_OBJECT, "hhomogeneous", { "vhomogeneous", NULL, NULL } }
};
int i, k, l;
PropKind kind;
int prop_name_index = 0;
GType type;
char *canonical_name;
kind = get_prop_kind (element);
class_name = get_class_name (element);
property_name = NULL;
for (i = 0; element->attribute_names[i]; i++)
{
if (strcmp (element->attribute_names[i], "name") == 0)
{
prop_name_index = i;
property_name = (const char *)element->attribute_values[i];
}
}
if (property_name == NULL)
return;
type = g_type_from_name (class_name);
canonical_name = g_strdup (property_name);
g_strdelimit (canonical_name, "_", '-');
for (k = 0; k < G_N_ELEMENTS (props); k++)
{
if (g_type_is_a (type, props[k].type) &&
strcmp (canonical_name, props[k].property) == 0 &&
kind == props[k].kind)
{
g_free (element->attribute_values[prop_name_index]);
element->attribute_values[prop_name_index] = g_strdup (props[k].new_name);
for (l = 0; l < 3 && props[k].alt_names[l]; l++)
{
Element *elt;
GList *sibling;
elt = g_new0 (Element, 1);
elt->parent = element->parent;
elt->element_name = g_strdup (element->element_name);
elt->attribute_names = g_strdupv ((char **) element->attribute_names);
elt->attribute_values = g_strdupv ((char **) element->attribute_values);
elt->data = g_strdup (element->data);
g_free (elt->attribute_values[prop_name_index]);
elt->attribute_values[prop_name_index] = g_strdup (props[k].alt_names[l]);
sibling = g_list_find (element->parent->children, element);
element->parent->children = g_list_insert_before (element->parent->children,
sibling,
elt);
}
break;
}
}
g_free (canonical_name);
}
static Element *
rewrite_stack_child (Element *child, MyParserData *data)
{
Element *object = NULL;
Element *packing = NULL;
Element *new_object;
Element *prop;
GList *l;
if (!g_str_equal (child->element_name, "child"))
return child;
for (l = child->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "object"))
object = elt;
else if (g_str_equal (elt->element_name, "packing"))
packing = elt;
}
if (!packing)
return child;
new_object = g_new0 (Element, 1);
new_object->element_name = g_strdup ("object");
new_object->attribute_names = g_new0 (char *, 2);
new_object->attribute_names[0] = g_strdup ("class");
new_object->attribute_values = g_new0 (char *, 2);
new_object->attribute_values[0] = g_strdup ("GtkStackPage");
new_object->children = packing->children;
new_object->parent = child;
packing->children = NULL;
prop = g_new0 (Element, 1);
prop->element_name = g_strdup ("property");
prop->attribute_names = g_new0 (char *, 2);
prop->attribute_names[0] = g_strdup ("name");
prop->attribute_values = g_new0 (char *, 2);
prop->attribute_values[0] = g_strdup ("child");
prop->children = g_list_append (prop->children, object);
prop->parent = new_object;
new_object->children = g_list_append (new_object->children, prop);
g_list_free (child->children);
child->children = g_list_append (NULL, new_object);
return child;
}
static void
rewrite_stack (Element *element,
MyParserData *data)
{
GList *l, *new_children;
new_children = NULL;
for (l = element->children; l; l = l->next)
{
Element *child = l->data;
new_children = g_list_append (new_children, rewrite_stack_child (child, data));
}
g_list_free (element->children);
element->children = new_children;
}
static Element *
rewrite_assistant_child (Element *child, MyParserData *data)
{
Element *object = NULL;
Element *packing = NULL;
Element *new_object;
Element *prop;
GList *l;
if (!g_str_equal (child->element_name, "child"))
return child;
for (l = child->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "object"))
object = elt;
else if (g_str_equal (elt->element_name, "packing"))
packing = elt;
}
if (!packing)
return child;
new_object = g_new0 (Element, 1);
new_object->element_name = g_strdup ("object");
new_object->attribute_names = g_new0 (char *, 2);
new_object->attribute_names[0] = g_strdup ("class");
new_object->attribute_values = g_new0 (char *, 2);
new_object->attribute_values[0] = g_strdup ("GtkAssistantPage");
new_object->children = packing->children;
new_object->parent = child;
packing->children = NULL;
prop = g_new0 (Element, 1);
prop->element_name = g_strdup ("property");
prop->attribute_names = g_new0 (char *, 2);
prop->attribute_names[0] = g_strdup ("name");
prop->attribute_values = g_new0 (char *, 2);
prop->attribute_values[0] = g_strdup ("child");
prop->children = g_list_append (prop->children, object);
prop->parent = new_object;
new_object->children = g_list_append (new_object->children, prop);
g_list_free (child->children);
child->children = g_list_append (NULL, new_object);
return child;
}
static void
rewrite_assistant (Element *element,
MyParserData *data)
{
GList *l, *new_children;
new_children = NULL;
for (l = element->children; l; l = l->next)
{
Element *child = l->data;
new_children = g_list_append (new_children, rewrite_assistant_child (child, data));
}
g_list_free (element->children);
element->children = new_children;
}
static Element *
rewrite_notebook_page (Element *child, Element *tab, MyParserData *data)
{
Element *object = NULL;
Element *tab_obj = NULL;
Element *packing = NULL;
Element *new_object;
Element *prop;
GList *l;
if (!g_str_equal (child->element_name, "child"))
return child;
if (has_attribute (child, "type", "tab") ||
has_attribute (child, "type", "action-start") ||
has_attribute (child, "type", "action-end"))
return child;
for (l = child->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "object"))
object = elt;
else if (g_str_equal (elt->element_name, "packing"))
packing = elt;
}
if (!packing && !tab)
return child;
if (tab)
{
for (l = tab->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "object"))
tab_obj = elt;
}
}
new_object = g_new0 (Element, 1);
new_object->element_name = g_strdup ("object");
new_object->attribute_names = g_new0 (char *, 2);
new_object->attribute_names[0] = g_strdup ("class");
new_object->attribute_values = g_new0 (char *, 2);
new_object->attribute_values[0] = g_strdup ("GtkNotebookPage");
new_object->parent = child;
if (packing)
{
new_object->children = packing->children;
packing->children = NULL;
}
prop = g_new0 (Element, 1);
prop->element_name = g_strdup ("property");
prop->attribute_names = g_new0 (char *, 2);
prop->attribute_names[0] = g_strdup ("name");
prop->attribute_values = g_new0 (char *, 2);
prop->attribute_values[0] = g_strdup ("child");
prop->children = g_list_append (prop->children, object);
prop->parent = new_object;
new_object->children = g_list_append (new_object->children, prop);
if (tab_obj)
{
prop = g_new0 (Element, 1);
prop->element_name = g_strdup ("property");
prop->attribute_names = g_new0 (char *, 2);
prop->attribute_names[0] = g_strdup ("name");
prop->attribute_values = g_new0 (char *, 2);
prop->attribute_values[0] = g_strdup ("tab");
prop->children = g_list_append (prop->children, tab_obj);
prop->parent = new_object;
new_object->children = g_list_append (new_object->children, prop);
}
g_list_free (child->children);
child->children = g_list_append (NULL, new_object);
return child;
}
static void
rewrite_notebook (Element *element,
MyParserData *data)
{
GList *l, *new_children;
new_children = NULL;
for (l = element->children; l; l = l->next)
{
Element *child = l->data;
Element *tab = l->next ? l->next->data : NULL;
if (tab && has_attribute (tab, "type", "tab"))
{
new_children = g_list_append (new_children, rewrite_notebook_page (child, tab, data));
l = l->next; /* skip the tab */
}
else
new_children = g_list_append (new_children, rewrite_notebook_page (child, NULL, data));
}
g_list_free (element->children);
element->children = new_children;
}
static void
rewrite_pack_type_child (Element *element,
MyParserData *data)
{
Element *pack_type = NULL;
GList *l, *ll;
if (!g_str_equal (element->element_name, "child"))
return;
for (l = element->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "packing"))
{
for (ll = elt->children; ll; ll = ll->next)
{
Element *elt2 = ll->data;
if (g_str_equal (elt2->element_name, "property") &&
has_attribute (elt2, "name", "pack-type"))
{
pack_type = elt2;
elt->children = g_list_remove (elt->children, pack_type);
if (elt->children == NULL)
{
element->children = g_list_remove (element->children, elt);
free_element (elt);
}
break;
}
}
}
if (pack_type)
break;
}
if (pack_type)
{
char **attnames = g_new0 (char *, g_strv_length (element->attribute_names) + 2);
char **attvalues = g_new0 (char *, g_strv_length (element->attribute_names) + 2);
int i;
for (i = 0; element->attribute_names[i]; i++)
{
attnames[i] = g_strdup (element->attribute_names[i]);
attvalues[i] = g_strdup (element->attribute_values[i]);
}
attnames[i] = g_strdup ("type");
attvalues[i] = g_strdup (pack_type->data);
g_strfreev (element->attribute_names);
g_strfreev (element->attribute_values);
element->attribute_names = attnames;
element->attribute_values = attvalues;
free_element (pack_type);
}
}
static void
rewrite_pack_type (Element *element,
MyParserData *data)
{
GList *l;
for (l = element->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "child"))
rewrite_pack_type_child (elt, data);
}
}
static void
rewrite_child_prop_to_prop_child (Element *element,
MyParserData *data,
const char *child_prop,
const char *prop)
{
Element *object = NULL;
Element *replaced = NULL;
GList *l, *ll;
if (!g_str_equal (element->element_name, "child"))
return;
for (l = element->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "object"))
object = elt;
if (g_str_equal (elt->element_name, "packing"))
{
for (ll = elt->children; ll; ll = ll->next)
{
Element *elt2 = ll->data;
if (g_str_equal (elt2->element_name, "property") &&
has_attribute (elt2, "name", child_prop))
{
replaced = elt2;
elt->children = g_list_remove (elt->children, replaced);
if (elt->children == NULL)
{
element->children = g_list_remove (element->children, elt);
free_element (elt);
}
break;
}
}
}
if (replaced)
break;
}
if (replaced)
{
Element *elt;
elt = g_new0 (Element, 1);
elt->parent = element;
elt->element_name = g_strdup ("property");
elt->attribute_names = g_new0 (char *, 2);
elt->attribute_names[0] = g_strdup ("name");
elt->attribute_values = g_new0 (char *, 2);
elt->attribute_values[0] = g_strdup (prop);
elt->data = g_strdup (replaced->data);
object->children = g_list_prepend (object->children, elt);
free_element (replaced);
}
}
static void
rewrite_child_prop_to_prop (Element *element,
MyParserData *data,
const char *child_prop,
const char *prop)
{
GList *l;
for (l = element->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "child"))
rewrite_child_prop_to_prop_child (elt, data, child_prop, prop);
}
}
static void
rewrite_paned_child (Element *element,
MyParserData *data,
Element *child,
const char *suffix)
{
Element *resize = NULL;
Element *shrink = NULL;
GList *l, *ll;
for (l = child->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "packing"))
{
for (ll = elt->children; ll; ll = ll->next)
{
Element *elt2 = ll->data;
if (g_str_equal (elt2->element_name, "property") &&
has_attribute (elt2, "name", "resize"))
resize = elt2;
else if (g_str_equal (elt2->element_name, "property") &&
has_attribute (elt2, "name", "shrink"))
shrink = elt2;
}
if (resize)
elt->children = g_list_remove (elt->children, resize);
if (shrink)
elt->children = g_list_remove (elt->children, shrink);
if (elt->children == NULL)
{
child->children = g_list_remove (child->children, elt);
free_element (elt);
}
}
if (resize || shrink)
break;
}
if (resize)
{
Element *elt;
elt = g_new0 (Element, 1);
elt->parent = element;
elt->element_name = g_strdup ("property");
elt->attribute_names = g_new0 (char *, 2);
elt->attribute_names[0] = g_strdup ("name");
elt->attribute_values = g_new0 (char *, 2);
elt->attribute_values[0] = g_strconcat ("resize-", suffix, NULL);
elt->data = g_strdup (resize->data);
element->children = g_list_prepend (element->children, elt);
free_element (resize);
}
if (shrink)
{
Element *elt;
elt = g_new0 (Element, 1);
elt->parent = element;
elt->element_name = g_strdup ("property");
elt->attribute_names = g_new0 (char *, 2);
elt->attribute_names[0] = g_strdup ("name");
elt->attribute_values = g_new0 (char *, 2);
elt->attribute_values[0] = g_strconcat ("shrink-", suffix, NULL);
elt->data = g_strdup (shrink->data);
element->children = g_list_prepend (element->children, elt);
free_element (shrink);
}
}
static void
rewrite_paned (Element *element,
MyParserData *data)
{
Element *child1 = NULL;
Element *child2 = NULL;
GList *l;
for (l = element->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "child"))
{
if (child1 == NULL)
child1 = elt;
else if (child2 == NULL)
child2 = elt;
else
break;
}
}
if (child1)
rewrite_paned_child (element, data, child1, "child1");
if (child2)
rewrite_paned_child (element, data, child2, "child2");
}
static void
rewrite_dialog (Element *element,
MyParserData *data)
{
Element *content_area = NULL;
Element *vbox = NULL;
Element *action_area = NULL;
GList *l;
for (l = element->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "child") &&
g_strcmp0 (get_attribute_value (elt, "internal-child"), "vbox") == 0)
{
content_area = elt;
break;
}
}
if (!content_area || !content_area->children)
return;
vbox = content_area->children->data;
for (l = vbox->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "child") &&
g_strcmp0 (get_attribute_value (elt, "internal-child"), "action_area") == 0)
{
action_area = elt;
break;
}
}
if (!action_area)
return;
set_attribute_value (content_area, "internal-child", "content_area");
vbox->children = g_list_remove (vbox->children, action_area);
action_area->parent = element;
element->children = g_list_append (element->children, action_area);
for (l = action_area->children; l; l = l->next)
{
Element *elt = l->data;
if (g_str_equal (elt->element_name, "packing"))
{
action_area->children = g_list_remove (action_area->children, elt);
free_element (elt);
break;
}
}
}
static void
rewrite_layout_props (Element *element,
MyParserData *data)
{
GList *l, *ll;
for (l = element->children; l; l = l->next)
{
Element *child = l->data;
if (g_str_equal (child->element_name, "child"))
{
Element *object = NULL;
Element *packing = NULL;
for (ll = child->children; ll; ll = ll->next)
{
Element *elt2 = ll->data;
if (g_str_equal (elt2->element_name, "object"))
object = elt2;
if (g_str_equal (elt2->element_name, "packing"))
packing = elt2;
}
if (object && packing)
{
child->children = g_list_remove (child->children, packing);
g_free (packing->element_name);
packing->element_name = g_strdup ("layout");
packing->parent = object;
object->children = g_list_append (object->children, packing);
}
}
}
}
static void
rewrite_grid_layout_prop (Element *element,
const char *attr_name,
const char *old_value,
const char *new_value)
{
char *canonical_name;
canonical_name = g_strdup (old_value);
g_strdelimit (canonical_name, "_", '-');
if (g_str_equal (element->element_name, "property"))
{
if (has_attribute (element, attr_name, old_value) ||
has_attribute (element, attr_name, canonical_name))
{
set_attribute_value (element, attr_name, new_value);
}
}
g_free (canonical_name);
}
static void
rewrite_grid_layout (Element *element,
MyParserData *data)
{
struct _Prop {
const char *attr_name;
const char *old_value;
const char *new_value;
} props[] = {
{ "name", "width", "column-span", },
{ "name", "height", "row-span", },
};
GList *l, *ll;
for (l = element->children; l; l = l->next)
{
Element *child = l->data;
if (g_str_equal (child->element_name, "child"))
{
Element *object = NULL;
Element *packing = NULL;
for (ll = child->children; ll; ll = ll->next)
{
Element *elt2 = ll->data;
if (g_str_equal (elt2->element_name, "object"))
object = elt2;
if (g_str_equal (elt2->element_name, "packing"))
packing = elt2;
}
if (object && packing)
{
int i;
child->children = g_list_remove (child->children, packing);
g_free (packing->element_name);
packing->element_name = g_strdup ("layout");
packing->parent = object;
object->children = g_list_append (object->children, packing);
for (ll = packing->children; ll; ll = ll->next)
{
Element *elt = ll->data;
for (i = 0; i < G_N_ELEMENTS (props); i++)
rewrite_grid_layout_prop (elt,
props[i].attr_name,
props[i].old_value,
props[i].new_value);
}
}
}
}
}
static void
rewrite_bin_child (Element *element,
MyParserData *data)
{
GList *l, *ll;
const char *class_name;
GType type;
for (l = element->children; l; l = l->next)
{
Element *child = l->data;
Element *object = NULL;
if (!g_str_equal (child->element_name, "child") ||
has_attribute (child, "type", NULL))
continue;
for (ll = child->children; ll; ll = ll->next)
{
Element *elem = ll->data;
if (!g_str_equal (elem->element_name, "object"))
continue;
class_name = get_attribute_value (elem, "class");
if (!class_name)
continue;
type = g_type_from_name (class_name);
if (!g_type_is_a (type, GTK_TYPE_WIDGET))
continue;
object = elem;
}
if (object)
{
g_free (child->element_name);
g_strfreev (child->attribute_names);
g_strfreev (child->attribute_values);
child->element_name = g_strdup ("property");
child->attribute_names = g_new0 (char *, 2);
child->attribute_names[0] = g_strdup ("name");
child->attribute_values = g_new0 (char *, 2);
child->attribute_values[0] = g_strdup ("child");
break;
}
}
}
/* returns TRUE to remove the element from the parent */
static gboolean
simplify_element (Element *element,
MyParserData *data)
{
GList *l;
if (!is_pcdata_element (element))
g_clear_pointer (&element->data, g_free);
else if (g_str_equal (element->element_name, "property") &&
property_is_boolean (element, data))
{
const char *b = canonical_boolean_value (data, element->data);
g_free (element->data);
element->data = g_strdup (b);
}
l = element->children;
while (l)
{
GList *next = l->next;
Element *child = l->data;
if (simplify_element (child, data))
{
element->children = g_list_remove (element->children, child);
free_element (child);
}
l = next;
}
if (is_container_element (element) && element->children == NULL)
return TRUE;
if (g_str_equal (element->element_name, "property") &&
property_can_be_omitted (element, data))
return TRUE;
if (g_str_equal (element->element_name, "binding"))
{
const char *property_name = get_attribute_value (element, "name");
const char *class_name = get_class_name (element);
if (!get_property_pspec (data, class_name, property_name, PROP_KIND_OBJECT))
warn_missing_property (element, data, class_name, property_name, PROP_KIND_OBJECT);
}
return FALSE;
}
static void
simplify_tree (MyParserData *data)
{
simplify_element (data->root, data);
}
static gboolean
rewrite_element (Element *element,
MyParserData *data)
{
GList *l;
l = element->children;
while (l)
{
GList *next = l->next;
Element *child = l->data;
if (rewrite_element (child, data))
{
element->children = g_list_remove (element->children, child);
free_element (child);
}
l = next;
}
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkStack"))
rewrite_stack (element, data);
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkAssistant"))
rewrite_assistant (element, data);
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkNotebook"))
rewrite_notebook (element, data);
if (element_is_object_or_template (element) &&
(g_str_equal (get_class_name (element), "GtkActionBar") ||
g_str_equal (get_class_name (element), "GtkHeaderBar")))
rewrite_pack_type (element, data);
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkPopoverMenu"))
rewrite_child_prop_to_prop (element, data, "submenu", "name");
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkToolbar"))
rewrite_child_prop_to_prop (element, data, "expand", "expand-item");
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkToolbar"))
rewrite_child_prop_to_prop (element, data, "homogeneous", "homogeneous");
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkPaned"))
rewrite_paned (element, data);
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkDialog"))
rewrite_dialog (element, data);
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkOverlay"))
rewrite_layout_props (element, data);
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkGrid"))
rewrite_grid_layout (element, data);
if (element_is_object_or_template (element) &&
g_str_equal (get_class_name (element), "GtkFixed"))
rewrite_layout_props (element, data);
if (element_is_object_or_template (element) &&
(g_str_equal (get_class_name (element), "GtkAspectFrame") ||
g_str_equal (get_class_name (element), "GtkComboBox") ||
g_str_equal (get_class_name (element), "GtkComboBoxText") ||
g_str_equal (get_class_name (element), "GtkFlowBoxChild") ||
g_str_equal (get_class_name (element), "GtkFrame") ||
g_str_equal (get_class_name (element), "GtkListBoxRow") ||
g_str_equal (get_class_name (element), "GtkOverlay") ||
g_str_equal (get_class_name (element), "GtkPopover") ||
g_str_equal (get_class_name (element), "GtkPopoverMenu") ||
g_str_equal (get_class_name (element), "GtkRevealer") ||
g_str_equal (get_class_name (element), "GtkScrolledWindow") ||
g_str_equal (get_class_name (element), "GtkSearchBar") ||
g_str_equal (get_class_name (element), "GtkViewport") ||
g_str_equal (get_class_name (element), "GtkWindow")))
rewrite_bin_child (element, data);
if (g_str_equal (element->element_name, "property"))
maybe_rename_property (element, data);
if (g_str_equal (element->element_name, "property") &&
property_has_been_removed (element, data))
return TRUE;
return FALSE;
}
static void
rewrite_tree (MyParserData *data)
{
rewrite_element (data->root, data);
}
/* For properties which have changed their default
* value between 3 and 4, we make sure that their
* old default value is present in the tree before
* simplifying it.
*
* So far, this is just GtkWidget::visible,
* changing its default from 0 to 1.
*/
static void
add_old_default_properties (Element *element,
MyParserData *data)
{
const char *class_name;
GType type;
if (!g_str_equal (element->element_name, "object"))
return;
class_name = get_class_name (element);
type = g_type_from_name (class_name);
if (g_type_is_a (type, GTK_TYPE_WIDGET))
{
GList *l;
gboolean has_visible = FALSE;
for (l = element->children; l; l = l->next)
{
Element *prop = l->data;
const char *name = get_attribute_value (prop, "name");
if (g_str_equal (prop->element_name, "property") &&
g_str_equal (name, "visible"))
has_visible = TRUE;
}
if (!has_visible)
{
Element *new_prop = g_new0 (Element, 1);
new_prop->parent = element;
new_prop->element_name = g_strdup ("property");
new_prop->attribute_names = g_new0 (char *, 2);
new_prop->attribute_names[0] = g_strdup ("name");
new_prop->attribute_values = g_new0 (char *, 2);
new_prop->attribute_values[0] = g_strdup ("visible");
new_prop->data = g_strdup ("0");
element->children = g_list_prepend (element->children, new_prop);
}
}
}
static void
enhance_element (Element *element,
MyParserData *data)
{
GList *l;
add_old_default_properties (element, data);
for (l = element->children; l; l = l->next)
{
Element *child = l->data;
enhance_element (child, data);
}
}
static void
enhance_tree (MyParserData *data)
{
enhance_element (data->root, data);
}
static void
dump_element (Element *element,
FILE *output,
int indent)
{
g_fprintf (output, "%*s<%s", indent, "", element->element_name);
if (element->attribute_names[0])
{
int i;
for (i = 0; element->attribute_names[i]; i++)
{
char *escaped = g_markup_escape_text (element->attribute_values[i], -1);
g_fprintf (output, " %s=\"%s\"", element->attribute_names[i], escaped);
g_free (escaped);
}
}
if (element->children || element->data)
{
g_fprintf (output, ">");
if (element->children)
{
GList *l;
g_fprintf (output, "\n");
for (l = element->children; l; l = l->next)
{
Element *child = l->data;
dump_element (child, output, indent + 2);
}
g_fprintf (output, "%*s", indent, "");
}
else
{
if (is_cdata_property (element))
{
g_fprintf (output, "<![CDATA[");
g_fprintf (output, "%s", element->data);
g_fprintf (output, "]]>");
}
else
{
char *escaped = g_markup_escape_text (element->data, -1);
g_fprintf (output, "%s", escaped);
g_free (escaped);
}
}
g_fprintf (output, "</%s>\n", element->element_name);
}
else
g_fprintf (output, "/>\n");
}
static void
write_xml_declaration (FILE *output)
{
g_fprintf (output, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
}
static void
dump_tree (MyParserData *data)
{
write_xml_declaration (data->output);
dump_element (data->root, data->output, 0);
}
static gboolean
simplify_file (const char *filename,
gboolean replace,
gboolean convert3to4)
{
GMarkupParseContext *context;
char *buffer;
MyParserData data;
GError *error = NULL;
data.input_filename = filename;
data.output_filename = NULL;
data.convert3to4 = convert3to4;
if (replace)
{
int fd;
fd = g_file_open_tmp ("gtk-builder-tool-XXXXXX", &data.output_filename, NULL);
data.output = fdopen (fd, "w");
}
else
{
data.output = stdout;
}
if (!g_file_get_contents (filename, &buffer, NULL, &error))
{
g_printerr (_("Cant load “%s”: %s\n"), filename, error->message);
return FALSE;
}
data.root = NULL;
data.current = NULL;
data.value = g_string_new ("");
context = g_markup_parse_context_new (&parser, G_MARKUP_TREAT_CDATA_AS_TEXT, &data, NULL);
if (!g_markup_parse_context_parse (context, buffer, -1, &error))
{
g_printerr (_("Cant parse “%s”: %s\n"), filename, error->message);
return FALSE;
}
data.builder = gtk_builder_new ();
if (data.convert3to4)
{
enhance_tree (&data);
rewrite_tree (&data);
}
simplify_tree (&data);
dump_tree (&data);
fclose (data.output);
if (data.output_filename)
{
char *content;
gsize length;
if (!g_file_get_contents (data.output_filename, &content, &length, &error))
{
g_printerr (_("Failed to read “%s”: %s\n"), data.output_filename, error->message);
return FALSE;
}
if (!g_file_set_contents (data.input_filename, content, length, &error))
{
g_printerr (_("Failed to write %s: “%s”\n"), data.input_filename, error->message);
return FALSE;
}
}
return TRUE;
}
void
do_simplify (int *argc,
const char ***argv)
{
gboolean replace = FALSE;
gboolean convert3to4 = FALSE;
char **filenames = NULL;
GOptionContext *ctx;
const GOptionEntry entries[] = {
{ "replace", 0, 0, G_OPTION_ARG_NONE, &replace, NULL, NULL },
{ "3to4", 0, 0, G_OPTION_ARG_NONE, &convert3to4, NULL, NULL },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL },
{ NULL, }
};
GError *error = NULL;
int i;
ctx = g_option_context_new (NULL);
g_option_context_set_help_enabled (ctx, FALSE);
g_option_context_add_main_entries (ctx, entries, NULL);
if (!g_option_context_parse (ctx, argc, (char ***)argv, &error))
{
g_printerr ("%s\n", error->message);
g_error_free (error);
exit (1);
}
g_option_context_free (ctx);
if (filenames == NULL)
{
g_printerr (_("No .ui file specified\n"));
exit (1);
}
if (g_strv_length (filenames) > 1 && !replace)
{
g_printerr (_("Can only simplify a single .ui file without --replace\n"));
exit (1);
}
for (i = 0; filenames[i]; i++)
{
if (!simplify_file (filenames[i], replace, convert3to4))
exit (1);
}
}