mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-27 07:56:14 +01:00
e56498ee0b
_GNU_SOURCE must be defined before including any other (system) header, so defining it in glib-unix.h (and hoping no one has included anything else before that) is wrong. And the "#define _USE_GNU" workaround for this problem in gnetworkingprivate.h is even wronger (and still prone to failure anyway due to single-include guards). Fix this by defining _GNU_SOURCE in config.h when building against glibc. In theory this is bad because new releases of glibc may include symbols that conflict with glib symbols, which could then cause compile failures. However, most people only see new releases of glibc when they upgrade their distro, at which point they also generally get new releases of gcc, which have new warnings/errors to clean up anyway. https://bugzilla.gnome.org/show_bug.cgi?id=649201
2071 lines
60 KiB
C
2071 lines
60 KiB
C
/*
|
|
* Copyright © 2010 Codethink Limited
|
|
*
|
|
* 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 of the licence, 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, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*
|
|
* Author: Ryan Lortie <desrt@desrt.ca>
|
|
*/
|
|
|
|
/* Prologue {{{1 */
|
|
#include "config.h"
|
|
|
|
#include <gstdio.h>
|
|
#include <gi18n.h>
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <locale.h>
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "gvdb/gvdb-builder.h"
|
|
#include "strinfo.c"
|
|
|
|
/* Handling of <enum> {{{1 */
|
|
typedef struct
|
|
{
|
|
GString *strinfo;
|
|
|
|
gboolean is_flags;
|
|
} EnumState;
|
|
|
|
static void
|
|
enum_state_free (gpointer data)
|
|
{
|
|
EnumState *state = data;
|
|
|
|
g_string_free (state->strinfo, TRUE);
|
|
g_slice_free (EnumState, state);
|
|
}
|
|
|
|
EnumState *
|
|
enum_state_new (gboolean is_flags)
|
|
{
|
|
EnumState *state;
|
|
|
|
state = g_slice_new (EnumState);
|
|
state->strinfo = g_string_new (NULL);
|
|
state->is_flags = is_flags;
|
|
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
enum_state_add_value (EnumState *state,
|
|
const gchar *nick,
|
|
const gchar *valuestr,
|
|
GError **error)
|
|
{
|
|
gint64 value;
|
|
gchar *end;
|
|
|
|
if (nick[0] == '\0' || nick[1] == '\0')
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"nick must be a minimum of 2 characters");
|
|
return;
|
|
}
|
|
|
|
value = g_ascii_strtoll (valuestr, &end, 0);
|
|
if (*end || state->is_flags ?
|
|
(value > G_MAXUINT32 || value < 0) :
|
|
(value > G_MAXINT32 || value < G_MININT32))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"invalid numeric value");
|
|
return;
|
|
}
|
|
|
|
if (strinfo_builder_contains (state->strinfo, nick))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<value nick='%s'/> already specified", nick);
|
|
return;
|
|
}
|
|
|
|
if (strinfo_builder_contains_value (state->strinfo, value))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"value='%s' already specified", valuestr);
|
|
return;
|
|
}
|
|
|
|
/* Silently drop the null case if it is mentioned.
|
|
* It is properly denoted with an empty array.
|
|
*/
|
|
if (state->is_flags && value == 0)
|
|
return;
|
|
|
|
if (state->is_flags && (value & (value - 1)))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"flags values must have at most 1 bit set");
|
|
return;
|
|
}
|
|
|
|
/* Since we reject exact duplicates of value='' and we only allow one
|
|
* bit to be set, it's not possible to have overlaps.
|
|
*
|
|
* If we loosen the one-bit-set restriction we need an overlap check.
|
|
*/
|
|
|
|
strinfo_builder_append_item (state->strinfo, nick, value);
|
|
}
|
|
|
|
static void
|
|
enum_state_end (EnumState **state_ptr,
|
|
GError **error)
|
|
{
|
|
EnumState *state;
|
|
|
|
state = *state_ptr;
|
|
*state_ptr = NULL;
|
|
|
|
if (state->strinfo->len == 0)
|
|
g_set_error (error,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<%s> must contain at least one <value>",
|
|
state->is_flags ? "flags" : "enum");
|
|
}
|
|
|
|
/* Handling of <key> {{{1 */
|
|
typedef struct
|
|
{
|
|
/* for <child>, @child_schema will be set.
|
|
* for <key>, everything else will be set.
|
|
*/
|
|
gchar *child_schema;
|
|
|
|
|
|
GVariantType *type;
|
|
gboolean have_gettext_domain;
|
|
|
|
gchar l10n;
|
|
gchar *l10n_context;
|
|
GString *unparsed_default_value;
|
|
GVariant *default_value;
|
|
|
|
GString *strinfo;
|
|
gboolean is_enum;
|
|
gboolean is_flags;
|
|
|
|
GVariant *minimum;
|
|
GVariant *maximum;
|
|
|
|
gboolean has_choices;
|
|
gboolean has_aliases;
|
|
gboolean is_override;
|
|
|
|
gboolean checked;
|
|
GVariant *serialised;
|
|
} KeyState;
|
|
|
|
static KeyState *
|
|
key_state_new (const gchar *type_string,
|
|
const gchar *gettext_domain,
|
|
gboolean is_enum,
|
|
gboolean is_flags,
|
|
GString *strinfo)
|
|
{
|
|
KeyState *state;
|
|
|
|
state = g_slice_new0 (KeyState);
|
|
state->type = g_variant_type_new (type_string);
|
|
state->have_gettext_domain = gettext_domain != NULL;
|
|
state->is_enum = is_enum;
|
|
state->is_flags = is_flags;
|
|
|
|
if (strinfo)
|
|
state->strinfo = g_string_new_len (strinfo->str, strinfo->len);
|
|
else
|
|
state->strinfo = g_string_new (NULL);
|
|
|
|
return state;
|
|
}
|
|
|
|
static KeyState *
|
|
key_state_override (KeyState *state,
|
|
const gchar *gettext_domain)
|
|
{
|
|
KeyState *copy;
|
|
|
|
copy = g_slice_new0 (KeyState);
|
|
copy->type = g_variant_type_copy (state->type);
|
|
copy->have_gettext_domain = gettext_domain != NULL;
|
|
copy->strinfo = g_string_new_len (state->strinfo->str,
|
|
state->strinfo->len);
|
|
copy->is_enum = state->is_enum;
|
|
copy->is_flags = state->is_flags;
|
|
copy->is_override = TRUE;
|
|
|
|
if (state->minimum)
|
|
{
|
|
copy->minimum = g_variant_ref (state->minimum);
|
|
copy->maximum = g_variant_ref (state->maximum);
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
static KeyState *
|
|
key_state_new_child (const gchar *child_schema)
|
|
{
|
|
KeyState *state;
|
|
|
|
state = g_slice_new0 (KeyState);
|
|
state->child_schema = g_strdup (child_schema);
|
|
|
|
return state;
|
|
}
|
|
|
|
static gboolean
|
|
is_valid_choices (GVariant *variant,
|
|
GString *strinfo)
|
|
{
|
|
switch (g_variant_classify (variant))
|
|
{
|
|
case G_VARIANT_CLASS_MAYBE:
|
|
case G_VARIANT_CLASS_ARRAY:
|
|
{
|
|
gboolean valid = TRUE;
|
|
GVariantIter iter;
|
|
|
|
g_variant_iter_init (&iter, variant);
|
|
|
|
while (valid && (variant = g_variant_iter_next_value (&iter)))
|
|
{
|
|
valid = is_valid_choices (variant, strinfo);
|
|
g_variant_unref (variant);
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
case G_VARIANT_CLASS_STRING:
|
|
return strinfo_is_string_valid ((const guint32 *) strinfo->str,
|
|
strinfo->len / 4,
|
|
g_variant_get_string (variant, NULL));
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
|
|
/* Gets called at </default> </choices> or <range/> to check for
|
|
* validity of the default value so that any inconsistency is
|
|
* reported as soon as it is encountered.
|
|
*/
|
|
static void
|
|
key_state_check_range (KeyState *state,
|
|
GError **error)
|
|
{
|
|
if (state->default_value)
|
|
{
|
|
const gchar *tag;
|
|
|
|
tag = state->is_override ? "override" : "default";
|
|
|
|
if (state->minimum)
|
|
{
|
|
if (g_variant_compare (state->default_value, state->minimum) < 0 ||
|
|
g_variant_compare (state->default_value, state->maximum) > 0)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<%s> is not contained in "
|
|
"the specified range", tag);
|
|
}
|
|
}
|
|
|
|
else if (state->strinfo->len)
|
|
{
|
|
if (!is_valid_choices (state->default_value, state->strinfo))
|
|
{
|
|
if (state->is_enum)
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<%s> is not a valid member of "
|
|
"the specified enumerated type", tag);
|
|
|
|
else if (state->is_flags)
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<%s> contains string not in the "
|
|
"specified flags type", tag);
|
|
|
|
else
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<%s> contains string not in "
|
|
"<choices>", tag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
key_state_set_range (KeyState *state,
|
|
const gchar *min_str,
|
|
const gchar *max_str,
|
|
GError **error)
|
|
{
|
|
const struct {
|
|
const gchar type;
|
|
const gchar *min;
|
|
const gchar *max;
|
|
} table[] = {
|
|
{ 'y', "0", "255" },
|
|
{ 'n', "-32768", "32767" },
|
|
{ 'q', "0", "65535" },
|
|
{ 'i', "-2147483648", "2147483647" },
|
|
{ 'u', "0", "4294967295" },
|
|
{ 'x', "-9223372036854775808", "9223372036854775807" },
|
|
{ 't', "0", "18446744073709551615" },
|
|
{ 'd', "-inf", "inf" },
|
|
};
|
|
gboolean type_ok = FALSE;
|
|
gint i;
|
|
|
|
if (state->minimum)
|
|
{
|
|
g_set_error_literal (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<range/> already specified for this key");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (table); i++)
|
|
if (*(char *) state->type == table[i].type)
|
|
{
|
|
min_str = min_str ? min_str : table[i].min;
|
|
max_str = max_str ? max_str : table[i].max;
|
|
type_ok = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (!type_ok)
|
|
{
|
|
gchar *type = g_variant_type_dup_string (state->type);
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<range> not allowed for keys of type '%s'", type);
|
|
g_free (type);
|
|
return;
|
|
}
|
|
|
|
state->minimum = g_variant_parse (state->type, min_str, NULL, NULL, error);
|
|
if (state->minimum == NULL)
|
|
return;
|
|
|
|
state->maximum = g_variant_parse (state->type, max_str, NULL, NULL, error);
|
|
if (state->maximum == NULL)
|
|
return;
|
|
|
|
if (g_variant_compare (state->minimum, state->maximum) > 0)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<range> specified minimum is greater than maxmimum");
|
|
return;
|
|
}
|
|
|
|
key_state_check_range (state, error);
|
|
}
|
|
|
|
static GString *
|
|
key_state_start_default (KeyState *state,
|
|
const gchar *l10n,
|
|
const gchar *context,
|
|
GError **error)
|
|
{
|
|
if (l10n != NULL)
|
|
{
|
|
if (strcmp (l10n, "messages") == 0)
|
|
state->l10n = 'm';
|
|
|
|
else if (strcmp (l10n, "time") == 0)
|
|
state->l10n = 't';
|
|
|
|
else
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"unsupported l10n category: %s", l10n);
|
|
return NULL;
|
|
}
|
|
|
|
if (!state->have_gettext_domain)
|
|
{
|
|
g_set_error_literal (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"l10n requested, but no "
|
|
"gettext domain given");
|
|
return NULL;
|
|
}
|
|
|
|
state->l10n_context = g_strdup (context);
|
|
}
|
|
|
|
else if (context != NULL)
|
|
{
|
|
g_set_error_literal (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"translation context given for "
|
|
" value without l10n enabled");
|
|
return NULL;
|
|
}
|
|
|
|
return g_string_new (NULL);
|
|
}
|
|
|
|
static void
|
|
key_state_end_default (KeyState *state,
|
|
GString **string,
|
|
GError **error)
|
|
{
|
|
state->unparsed_default_value = *string;
|
|
*string = NULL;
|
|
|
|
state->default_value = g_variant_parse (state->type,
|
|
state->unparsed_default_value->str,
|
|
NULL, NULL, error);
|
|
key_state_check_range (state, error);
|
|
}
|
|
|
|
static void
|
|
key_state_start_choices (KeyState *state,
|
|
GError **error)
|
|
{
|
|
const GVariantType *type = state->type;
|
|
|
|
if (state->is_enum)
|
|
{
|
|
g_set_error_literal (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<choices> can not be specified for keys "
|
|
"tagged as having an enumerated type");
|
|
return;
|
|
}
|
|
|
|
if (state->has_choices)
|
|
{
|
|
g_set_error_literal (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<choices> already specified for this key");
|
|
return;
|
|
}
|
|
|
|
while (g_variant_type_is_maybe (type) || g_variant_type_is_array (type))
|
|
type = g_variant_type_element (type);
|
|
|
|
if (!g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
|
|
{
|
|
gchar *type_string = g_variant_type_dup_string (state->type);
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<choices> not allowed for keys of type '%s'",
|
|
type_string);
|
|
g_free (type_string);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
key_state_add_choice (KeyState *state,
|
|
const gchar *choice,
|
|
GError **error)
|
|
{
|
|
if (strinfo_builder_contains (state->strinfo, choice))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<choice value='%s'/> already given", choice);
|
|
return;
|
|
}
|
|
|
|
strinfo_builder_append_item (state->strinfo, choice, 0);
|
|
state->has_choices = TRUE;
|
|
}
|
|
|
|
static void
|
|
key_state_end_choices (KeyState *state,
|
|
GError **error)
|
|
{
|
|
if (!state->has_choices)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<choices> must contain at least one <choice>");
|
|
return;
|
|
}
|
|
|
|
key_state_check_range (state, error);
|
|
}
|
|
|
|
static void
|
|
key_state_start_aliases (KeyState *state,
|
|
GError **error)
|
|
{
|
|
if (state->has_aliases)
|
|
g_set_error_literal (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<aliases> already specified for this key");
|
|
else if (!state->is_flags && !state->is_enum && !state->has_choices)
|
|
g_set_error_literal (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<aliases> can only be specified for keys with "
|
|
"enumerated or flags types or after <choices>");
|
|
}
|
|
|
|
static void
|
|
key_state_add_alias (KeyState *state,
|
|
const gchar *alias,
|
|
const gchar *target,
|
|
GError **error)
|
|
{
|
|
if (strinfo_builder_contains (state->strinfo, alias))
|
|
{
|
|
if (strinfo_is_string_valid ((guint32 *) state->strinfo->str,
|
|
state->strinfo->len / 4,
|
|
alias))
|
|
{
|
|
if (state->is_enum)
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<alias value='%s'/> given when '%s' is already "
|
|
"a member of the enumerated type", alias, alias);
|
|
|
|
else
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<alias value='%s'/> given when "
|
|
"<choice value='%s'/> was already given",
|
|
alias, alias);
|
|
}
|
|
|
|
else
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<alias value='%s'/> already specified", alias);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!strinfo_builder_append_alias (state->strinfo, alias, target))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"alias target '%s' is not in %s", target,
|
|
state->is_enum ? "enumerated type" : "<choices>");
|
|
return;
|
|
}
|
|
|
|
state->has_aliases = TRUE;
|
|
}
|
|
|
|
static void
|
|
key_state_end_aliases (KeyState *state,
|
|
GError **error)
|
|
{
|
|
if (!state->has_aliases)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"<aliases> must contain at least one <alias>");
|
|
return;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
key_state_check (KeyState *state,
|
|
GError **error)
|
|
{
|
|
if (state->checked)
|
|
return TRUE;
|
|
|
|
return state->checked = TRUE;
|
|
}
|
|
|
|
static GVariant *
|
|
key_state_serialise (KeyState *state)
|
|
{
|
|
if (state->serialised == NULL)
|
|
{
|
|
if (state->child_schema)
|
|
{
|
|
state->serialised = g_variant_new_string (state->child_schema);
|
|
}
|
|
|
|
else
|
|
{
|
|
GVariantBuilder builder;
|
|
|
|
g_assert (key_state_check (state, NULL));
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
|
|
|
|
/* default value */
|
|
g_variant_builder_add_value (&builder, state->default_value);
|
|
|
|
/* translation */
|
|
if (state->l10n)
|
|
{
|
|
if (state->l10n_context)
|
|
{
|
|
gint len;
|
|
|
|
/* Contextified messages are supported by prepending
|
|
* the context, followed by '\004' to the start of the
|
|
* message string. We do that here to save GSettings
|
|
* the work later on.
|
|
*/
|
|
len = strlen (state->l10n_context);
|
|
state->l10n_context[len] = '\004';
|
|
g_string_prepend_len (state->unparsed_default_value,
|
|
state->l10n_context, len + 1);
|
|
g_free (state->l10n_context);
|
|
state->l10n_context = NULL;
|
|
}
|
|
|
|
g_variant_builder_add (&builder, "(y(y&s))", 'l', state->l10n,
|
|
state->unparsed_default_value->str);
|
|
g_string_free (state->unparsed_default_value, TRUE);
|
|
state->unparsed_default_value = NULL;
|
|
}
|
|
|
|
/* choice, aliases, enums */
|
|
if (state->strinfo->len)
|
|
{
|
|
GVariant *array;
|
|
guint32 *words;
|
|
gpointer data;
|
|
gsize size;
|
|
gint i;
|
|
|
|
data = state->strinfo->str;
|
|
size = state->strinfo->len;
|
|
|
|
words = data;
|
|
for (i = 0; i < size / sizeof (guint32); i++)
|
|
words[i] = GUINT32_TO_LE (words[i]);
|
|
|
|
array = g_variant_new_from_data (G_VARIANT_TYPE ("au"),
|
|
data, size, TRUE,
|
|
g_free, data);
|
|
|
|
g_string_free (state->strinfo, FALSE);
|
|
state->strinfo = NULL;
|
|
|
|
g_variant_builder_add (&builder, "(y@au)",
|
|
state->is_flags ? 'f' :
|
|
state->is_enum ? 'e' : 'c',
|
|
array);
|
|
}
|
|
|
|
/* range */
|
|
if (state->minimum || state->maximum)
|
|
g_variant_builder_add (&builder, "(y(**))", 'r',
|
|
state->minimum, state->maximum);
|
|
|
|
state->serialised = g_variant_builder_end (&builder);
|
|
}
|
|
|
|
g_variant_ref_sink (state->serialised);
|
|
}
|
|
|
|
return g_variant_ref (state->serialised);
|
|
}
|
|
|
|
static void
|
|
key_state_free (gpointer data)
|
|
{
|
|
KeyState *state = data;
|
|
|
|
if (state->type)
|
|
g_variant_type_free (state->type);
|
|
|
|
g_free (state->l10n_context);
|
|
|
|
if (state->unparsed_default_value)
|
|
g_string_free (state->unparsed_default_value, TRUE);
|
|
|
|
if (state->default_value)
|
|
g_variant_unref (state->default_value);
|
|
|
|
if (state->strinfo)
|
|
g_string_free (state->strinfo, TRUE);
|
|
|
|
if (state->minimum)
|
|
g_variant_unref (state->minimum);
|
|
|
|
if (state->maximum)
|
|
g_variant_unref (state->maximum);
|
|
|
|
if (state->serialised)
|
|
g_variant_unref (state->serialised);
|
|
|
|
g_slice_free (KeyState, state);
|
|
}
|
|
|
|
/* Key name validity {{{1 */
|
|
static gboolean allow_any_name = FALSE;
|
|
|
|
static gboolean
|
|
is_valid_keyname (const gchar *key,
|
|
GError **error)
|
|
{
|
|
gint i;
|
|
|
|
if (key[0] == '\0')
|
|
{
|
|
g_set_error_literal (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("empty names are not permitted"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (allow_any_name)
|
|
return TRUE;
|
|
|
|
if (!g_ascii_islower (key[0]))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("invalid name '%s': names must begin "
|
|
"with a lowercase letter"), key);
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 1; key[i]; i++)
|
|
{
|
|
if (key[i] != '-' &&
|
|
!g_ascii_islower (key[i]) &&
|
|
!g_ascii_isdigit (key[i]))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("invalid name '%s': invalid character '%c'; "
|
|
"only lowercase letters, numbers and dash ('-') "
|
|
"are permitted."), key, key[i]);
|
|
return FALSE;
|
|
}
|
|
|
|
if (key[i] == '-' && key[i + 1] == '-')
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("invalid name '%s': two successive dashes ('--') "
|
|
"are not permitted."), key);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (key[i - 1] == '-')
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("invalid name '%s': the last character may not be a "
|
|
"dash ('-')."), key);
|
|
return FALSE;
|
|
}
|
|
|
|
if (i > 32)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("invalid name '%s': maximum length is 32"), key);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Handling of <schema> {{{1 */
|
|
typedef struct _SchemaState SchemaState;
|
|
struct _SchemaState
|
|
{
|
|
SchemaState *extends;
|
|
|
|
gchar *path;
|
|
gchar *gettext_domain;
|
|
gchar *extends_name;
|
|
gchar *list_of;
|
|
|
|
GHashTable *keys;
|
|
};
|
|
|
|
static SchemaState *
|
|
schema_state_new (const gchar *path,
|
|
const gchar *gettext_domain,
|
|
SchemaState *extends,
|
|
const gchar *extends_name,
|
|
const gchar *list_of)
|
|
{
|
|
SchemaState *state;
|
|
|
|
state = g_slice_new (SchemaState);
|
|
state->path = g_strdup (path);
|
|
state->gettext_domain = g_strdup (gettext_domain);
|
|
state->extends = extends;
|
|
state->extends_name = g_strdup (extends_name);
|
|
state->list_of = g_strdup (list_of);
|
|
state->keys = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, key_state_free);
|
|
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
schema_state_free (gpointer data)
|
|
{
|
|
SchemaState *state = data;
|
|
|
|
g_free (state->path);
|
|
g_free (state->gettext_domain);
|
|
g_hash_table_unref (state->keys);
|
|
}
|
|
|
|
static void
|
|
schema_state_add_child (SchemaState *state,
|
|
const gchar *name,
|
|
const gchar *schema,
|
|
GError **error)
|
|
{
|
|
gchar *childname;
|
|
|
|
if (!is_valid_keyname (name, error))
|
|
return;
|
|
|
|
childname = g_strconcat (name, "/", NULL);
|
|
|
|
if (g_hash_table_lookup (state->keys, childname))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<child name='%s'> already specified"), name);
|
|
return;
|
|
}
|
|
|
|
g_hash_table_insert (state->keys, childname,
|
|
key_state_new_child (schema));
|
|
}
|
|
|
|
static KeyState *
|
|
schema_state_add_key (SchemaState *state,
|
|
GHashTable *enum_table,
|
|
GHashTable *flags_table,
|
|
const gchar *name,
|
|
const gchar *type_string,
|
|
const gchar *enum_type,
|
|
const gchar *flags_type,
|
|
GError **error)
|
|
{
|
|
SchemaState *node;
|
|
GString *strinfo;
|
|
KeyState *key;
|
|
|
|
if (state->list_of)
|
|
{
|
|
g_set_error_literal (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("can not add keys to a 'list-of' schema"));
|
|
return NULL;
|
|
}
|
|
|
|
if (!is_valid_keyname (name, error))
|
|
return NULL;
|
|
|
|
if (g_hash_table_lookup (state->keys, name))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<key name='%s'> already specified"), name);
|
|
return NULL;
|
|
}
|
|
|
|
for (node = state; node; node = node->extends)
|
|
if (node->extends)
|
|
{
|
|
KeyState *shadow;
|
|
|
|
shadow = g_hash_table_lookup (node->extends->keys, name);
|
|
|
|
/* in case of <key> <override> <key> make sure we report the
|
|
* location of the original <key>, not the <override>.
|
|
*/
|
|
if (shadow && !shadow->is_override)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<key name='%s'> shadows <key name='%s'> in "
|
|
"<schema id='%s'>; use <override> to modify value"),
|
|
name, name, node->extends_name);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if ((type_string != NULL) + (enum_type != NULL) + (flags_type != NULL) != 1)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
|
|
_("exactly one of 'type', 'enum' or 'flags' must "
|
|
"be specified as an attribute to <key>"));
|
|
return NULL;
|
|
}
|
|
|
|
if (type_string == NULL) /* flags or enums was specified */
|
|
{
|
|
EnumState *enum_state;
|
|
|
|
if (enum_type)
|
|
enum_state = g_hash_table_lookup (enum_table, enum_type);
|
|
else
|
|
enum_state = g_hash_table_lookup (flags_table, flags_type);
|
|
|
|
|
|
if (enum_state == NULL)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<%s id='%s'> not (yet) defined."),
|
|
flags_type ? "flags" : "enum",
|
|
flags_type ? flags_type : enum_type);
|
|
return NULL;
|
|
}
|
|
|
|
type_string = flags_type ? "as" : "s";
|
|
strinfo = enum_state->strinfo;
|
|
}
|
|
else
|
|
{
|
|
if (!g_variant_type_string_is_valid (type_string))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("invalid GVariant type string '%s'"), type_string);
|
|
return NULL;
|
|
}
|
|
|
|
strinfo = NULL;
|
|
}
|
|
|
|
key = key_state_new (type_string, state->gettext_domain,
|
|
enum_type != NULL, flags_type != NULL, strinfo);
|
|
g_hash_table_insert (state->keys, g_strdup (name), key);
|
|
|
|
return key;
|
|
}
|
|
|
|
static void
|
|
schema_state_add_override (SchemaState *state,
|
|
KeyState **key_state,
|
|
GString **string,
|
|
const gchar *key,
|
|
const gchar *l10n,
|
|
const gchar *context,
|
|
GError **error)
|
|
{
|
|
SchemaState *parent;
|
|
KeyState *original;
|
|
|
|
if (state->extends == NULL)
|
|
{
|
|
g_set_error_literal (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<override> given but schema isn't "
|
|
"extending anything"));
|
|
return;
|
|
}
|
|
|
|
for (parent = state->extends; parent; parent = parent->extends)
|
|
if ((original = g_hash_table_lookup (parent->keys, key)))
|
|
break;
|
|
|
|
if (original == NULL)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("no <key name='%s'> to override"), key);
|
|
return;
|
|
}
|
|
|
|
if (g_hash_table_lookup (state->keys, key))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<override name='%s'> already specified"), key);
|
|
return;
|
|
}
|
|
|
|
*key_state = key_state_override (original, state->gettext_domain);
|
|
*string = key_state_start_default (*key_state, l10n, context, error);
|
|
g_hash_table_insert (state->keys, g_strdup (key), *key_state);
|
|
}
|
|
|
|
static void
|
|
override_state_end (KeyState **key_state,
|
|
GString **string,
|
|
GError **error)
|
|
{
|
|
key_state_end_default (*key_state, string, error);
|
|
*key_state = NULL;
|
|
}
|
|
|
|
/* Handling of toplevel state {{{1 */
|
|
typedef struct
|
|
{
|
|
GHashTable *schema_table; /* string -> SchemaState */
|
|
GHashTable *flags_table; /* string -> EnumState */
|
|
GHashTable *enum_table; /* string -> EnumState */
|
|
|
|
GSList *this_file_schemas; /* strings: <schema>s in this file */
|
|
GSList *this_file_flagss; /* strings: <flags>s in this file */
|
|
GSList *this_file_enums; /* strings: <enum>s in this file */
|
|
|
|
gchar *schemalist_domain; /* the <schemalist> gettext domain */
|
|
|
|
SchemaState *schema_state; /* non-NULL when inside <schema> */
|
|
KeyState *key_state; /* non-NULL when inside <key> */
|
|
EnumState *enum_state; /* non-NULL when inside <enum> */
|
|
|
|
GString *string; /* non-NULL when accepting text */
|
|
} ParseState;
|
|
|
|
static gboolean
|
|
is_subclass (const gchar *class_name,
|
|
const gchar *possible_parent,
|
|
GHashTable *schema_table)
|
|
{
|
|
SchemaState *class;
|
|
|
|
if (strcmp (class_name, possible_parent) == 0)
|
|
return TRUE;
|
|
|
|
class = g_hash_table_lookup (schema_table, class_name);
|
|
g_assert (class != NULL);
|
|
|
|
return class->extends_name &&
|
|
is_subclass (class->extends_name, possible_parent, schema_table);
|
|
}
|
|
|
|
static void
|
|
parse_state_start_schema (ParseState *state,
|
|
const gchar *id,
|
|
const gchar *path,
|
|
const gchar *gettext_domain,
|
|
const gchar *extends_name,
|
|
const gchar *list_of,
|
|
GError **error)
|
|
{
|
|
SchemaState *extends;
|
|
gchar *my_id;
|
|
|
|
if (g_hash_table_lookup (state->schema_table, id))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<schema id='%s'> already specified"), id);
|
|
return;
|
|
}
|
|
|
|
if (extends_name)
|
|
{
|
|
extends = g_hash_table_lookup (state->schema_table, extends_name);
|
|
|
|
if (extends == NULL)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<schema id='%s'> extends not yet "
|
|
"existing schema '%s'"), id, extends_name);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
extends = NULL;
|
|
|
|
if (list_of)
|
|
{
|
|
SchemaState *tmp;
|
|
|
|
if (!(tmp = g_hash_table_lookup (state->schema_table, list_of)))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<schema id='%s'> is list of not yet "
|
|
"existing schema '%s'"), id, list_of);
|
|
return;
|
|
}
|
|
|
|
if (tmp->path)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("Can not be a list of a schema with a path"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (extends)
|
|
{
|
|
if (extends->path)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("Can not extend a schema with a path"));
|
|
return;
|
|
}
|
|
|
|
if (list_of)
|
|
{
|
|
if (extends->list_of == NULL)
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<schema id='%s'> is a list, extending "
|
|
"<schema id='%s'> which is not a list"),
|
|
id, extends_name);
|
|
return;
|
|
}
|
|
|
|
if (!is_subclass (list_of, extends->list_of, state->schema_table))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<schema id='%s' list-of='%s'> extends <schema "
|
|
"id='%s' list-of='%s'> but '%s' does not "
|
|
"extend '%s'"), id, list_of, extends_name,
|
|
extends->list_of, list_of, extends->list_of);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
/* by default we are a list of the same thing that the schema
|
|
* we are extending is a list of (which might be nothing)
|
|
*/
|
|
list_of = extends->list_of;
|
|
}
|
|
|
|
if (path && !(g_str_has_prefix (path, "/") && g_str_has_suffix (path, "/")))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("a path, if given, must begin and end with a slash"));
|
|
return;
|
|
}
|
|
|
|
if (path && list_of && !g_str_has_suffix (path, ":/"))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("the path of a list must end with ':/'"));
|
|
return;
|
|
}
|
|
|
|
state->schema_state = schema_state_new (path, gettext_domain,
|
|
extends, extends_name, list_of);
|
|
|
|
my_id = g_strdup (id);
|
|
state->this_file_schemas = g_slist_prepend (state->this_file_schemas, my_id);
|
|
g_hash_table_insert (state->schema_table, my_id, state->schema_state);
|
|
}
|
|
|
|
static void
|
|
parse_state_start_enum (ParseState *state,
|
|
const gchar *id,
|
|
gboolean is_flags,
|
|
GError **error)
|
|
{
|
|
GSList **list = is_flags ? &state->this_file_flagss : &state->this_file_enums;
|
|
GHashTable *table = is_flags ? state->flags_table : state->enum_table;
|
|
gchar *my_id;
|
|
|
|
if (g_hash_table_lookup (table, id))
|
|
{
|
|
g_set_error (error, G_MARKUP_ERROR,
|
|
G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("<%s id='%s'> already specified"),
|
|
is_flags ? "flags" : "enum", id);
|
|
return;
|
|
}
|
|
|
|
state->enum_state = enum_state_new (is_flags);
|
|
|
|
my_id = g_strdup (id);
|
|
*list = g_slist_prepend (*list, my_id);
|
|
g_hash_table_insert (table, my_id, state->enum_state);
|
|
}
|
|
|
|
/* GMarkup Parser Functions {{{1 */
|
|
|
|
/* Start element {{{2 */
|
|
static void
|
|
start_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
const gchar **attribute_names,
|
|
const gchar **attribute_values,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParseState *state = user_data;
|
|
const GSList *element_stack;
|
|
const gchar *container;
|
|
|
|
element_stack = g_markup_parse_context_get_element_stack (context);
|
|
container = element_stack->next ? element_stack->next->data : NULL;
|
|
|
|
#define COLLECT(first, ...) \
|
|
g_markup_collect_attributes (element_name, \
|
|
attribute_names, attribute_values, error, \
|
|
first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID)
|
|
#define OPTIONAL G_MARKUP_COLLECT_OPTIONAL
|
|
#define STRDUP G_MARKUP_COLLECT_STRDUP
|
|
#define STRING G_MARKUP_COLLECT_STRING
|
|
#define NO_ATTRS() COLLECT (G_MARKUP_COLLECT_INVALID, NULL)
|
|
|
|
/* Toplevel items {{{3 */
|
|
if (container == NULL)
|
|
{
|
|
if (strcmp (element_name, "schemalist") == 0)
|
|
{
|
|
COLLECT (OPTIONAL | STRDUP,
|
|
"gettext-domain",
|
|
&state->schemalist_domain);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/* children of <schemalist> {{{3 */
|
|
else if (strcmp (container, "schemalist") == 0)
|
|
{
|
|
if (strcmp (element_name, "schema") == 0)
|
|
{
|
|
const gchar *id, *path, *gettext_domain, *extends, *list_of;
|
|
if (COLLECT (STRING, "id", &id,
|
|
OPTIONAL | STRING, "path", &path,
|
|
OPTIONAL | STRING, "gettext-domain", &gettext_domain,
|
|
OPTIONAL | STRING, "extends", &extends,
|
|
OPTIONAL | STRING, "list-of", &list_of))
|
|
parse_state_start_schema (state, id, path,
|
|
gettext_domain ? gettext_domain
|
|
: state->schemalist_domain,
|
|
extends, list_of, error);
|
|
return;
|
|
}
|
|
|
|
else if (strcmp (element_name, "enum") == 0)
|
|
{
|
|
const gchar *id;
|
|
if (COLLECT (STRING, "id", &id))
|
|
parse_state_start_enum (state, id, FALSE, error);
|
|
return;
|
|
}
|
|
|
|
else if (strcmp (element_name, "flags") == 0)
|
|
{
|
|
const gchar *id;
|
|
if (COLLECT (STRING, "id", &id))
|
|
parse_state_start_enum (state, id, TRUE, error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/* children of <schema> {{{3 */
|
|
else if (strcmp (container, "schema") == 0)
|
|
{
|
|
if (strcmp (element_name, "key") == 0)
|
|
{
|
|
const gchar *name, *type_string, *enum_type, *flags_type;
|
|
|
|
if (COLLECT (STRING, "name", &name,
|
|
OPTIONAL | STRING, "type", &type_string,
|
|
OPTIONAL | STRING, "enum", &enum_type,
|
|
OPTIONAL | STRING, "flags", &flags_type))
|
|
|
|
state->key_state = schema_state_add_key (state->schema_state,
|
|
state->enum_table,
|
|
state->flags_table,
|
|
name, type_string,
|
|
enum_type, flags_type,
|
|
error);
|
|
return;
|
|
}
|
|
else if (strcmp (element_name, "child") == 0)
|
|
{
|
|
const gchar *name, *schema;
|
|
|
|
if (COLLECT (STRING, "name", &name, STRING, "schema", &schema))
|
|
schema_state_add_child (state->schema_state,
|
|
name, schema, error);
|
|
return;
|
|
}
|
|
else if (strcmp (element_name, "override") == 0)
|
|
{
|
|
const gchar *name, *l10n, *context;
|
|
|
|
if (COLLECT (STRING, "name", &name,
|
|
OPTIONAL | STRING, "l10n", &l10n,
|
|
OPTIONAL | STRING, "context", &context))
|
|
schema_state_add_override (state->schema_state,
|
|
&state->key_state, &state->string,
|
|
name, l10n, context, error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* children of <key> {{{3 */
|
|
else if (strcmp (container, "key") == 0)
|
|
{
|
|
if (strcmp (element_name, "default") == 0)
|
|
{
|
|
const gchar *l10n, *context;
|
|
if (COLLECT (STRING | OPTIONAL, "l10n", &l10n,
|
|
STRING | OPTIONAL, "context", &context))
|
|
state->string = key_state_start_default (state->key_state,
|
|
l10n, context, error);
|
|
return;
|
|
}
|
|
|
|
else if (strcmp (element_name, "summary") == 0 ||
|
|
strcmp (element_name, "description") == 0)
|
|
{
|
|
if (NO_ATTRS ())
|
|
state->string = g_string_new (NULL);
|
|
return;
|
|
}
|
|
|
|
else if (strcmp (element_name, "range") == 0)
|
|
{
|
|
const gchar *min, *max;
|
|
if (COLLECT (STRING | OPTIONAL, "min", &min,
|
|
STRING | OPTIONAL, "max", &max))
|
|
key_state_set_range (state->key_state, min, max, error);
|
|
return;
|
|
}
|
|
|
|
else if (strcmp (element_name, "choices") == 0)
|
|
{
|
|
if (NO_ATTRS ())
|
|
key_state_start_choices (state->key_state, error);
|
|
return;
|
|
}
|
|
|
|
else if (strcmp (element_name, "aliases") == 0)
|
|
{
|
|
if (NO_ATTRS ())
|
|
key_state_start_aliases (state->key_state, error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/* children of <choices> {{{3 */
|
|
else if (strcmp (container, "choices") == 0)
|
|
{
|
|
if (strcmp (element_name, "choice") == 0)
|
|
{
|
|
const gchar *value;
|
|
if (COLLECT (STRING, "value", &value))
|
|
key_state_add_choice (state->key_state, value, error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/* children of <aliases> {{{3 */
|
|
else if (strcmp (container, "aliases") == 0)
|
|
{
|
|
if (strcmp (element_name, "alias") == 0)
|
|
{
|
|
const gchar *value, *target;
|
|
if (COLLECT (STRING, "value", &value, STRING, "target", &target))
|
|
key_state_add_alias (state->key_state, value, target, error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/* children of <enum> {{{3 */
|
|
else if (strcmp (container, "enum") == 0 ||
|
|
strcmp (container, "flags") == 0)
|
|
{
|
|
if (strcmp (element_name, "value") == 0)
|
|
{
|
|
const gchar *nick, *valuestr;
|
|
if (COLLECT (STRING, "nick", &nick,
|
|
STRING, "value", &valuestr))
|
|
enum_state_add_value (state->enum_state, nick, valuestr, error);
|
|
return;
|
|
}
|
|
}
|
|
/* 3}}} */
|
|
|
|
if (container)
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
|
|
_("Element <%s> not allowed inside <%s>"),
|
|
element_name, container);
|
|
else
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
|
|
_("Element <%s> not allowed at toplevel"), element_name);
|
|
}
|
|
/* 2}}} */
|
|
/* End element {{{2 */
|
|
|
|
static void
|
|
key_state_end (KeyState **state_ptr,
|
|
GError **error)
|
|
{
|
|
KeyState *state;
|
|
|
|
state = *state_ptr;
|
|
*state_ptr = NULL;
|
|
|
|
if (state->default_value == NULL)
|
|
{
|
|
g_set_error_literal (error,
|
|
G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
"element <default> is required in <key>");
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
schema_state_end (SchemaState **state_ptr,
|
|
GError **error)
|
|
{
|
|
*state_ptr = NULL;
|
|
}
|
|
|
|
static void
|
|
end_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParseState *state = user_data;
|
|
|
|
if (strcmp (element_name, "schemalist") == 0)
|
|
{
|
|
g_free (state->schemalist_domain);
|
|
state->schemalist_domain = NULL;
|
|
}
|
|
|
|
else if (strcmp (element_name, "enum") == 0 ||
|
|
strcmp (element_name, "flags") == 0)
|
|
enum_state_end (&state->enum_state, error);
|
|
|
|
else if (strcmp (element_name, "schema") == 0)
|
|
schema_state_end (&state->schema_state, error);
|
|
|
|
else if (strcmp (element_name, "override") == 0)
|
|
override_state_end (&state->key_state, &state->string, error);
|
|
|
|
else if (strcmp (element_name, "key") == 0)
|
|
key_state_end (&state->key_state, error);
|
|
|
|
else if (strcmp (element_name, "default") == 0)
|
|
key_state_end_default (state->key_state, &state->string, error);
|
|
|
|
else if (strcmp (element_name, "choices") == 0)
|
|
key_state_end_choices (state->key_state, error);
|
|
|
|
else if (strcmp (element_name, "aliases") == 0)
|
|
key_state_end_aliases (state->key_state, error);
|
|
|
|
if (state->string)
|
|
{
|
|
g_string_free (state->string, TRUE);
|
|
state->string = NULL;
|
|
}
|
|
}
|
|
/* Text {{{2 */
|
|
static void
|
|
text (GMarkupParseContext *context,
|
|
const gchar *text,
|
|
gsize text_len,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParseState *state = user_data;
|
|
gsize i;
|
|
|
|
for (i = 0; i < text_len; i++)
|
|
if (!g_ascii_isspace (text[i]))
|
|
{
|
|
if (state->string)
|
|
g_string_append_len (state->string, text, text_len);
|
|
|
|
else
|
|
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
|
|
_("text may not appear inside <%s>"),
|
|
g_markup_parse_context_get_element (context));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Write to GVDB {{{1 */
|
|
typedef struct
|
|
{
|
|
GHashTable *table;
|
|
GvdbItem *root;
|
|
} GvdbPair;
|
|
|
|
static void
|
|
gvdb_pair_init (GvdbPair *pair)
|
|
{
|
|
pair->table = gvdb_hash_table_new (NULL, NULL);
|
|
pair->root = gvdb_hash_table_insert (pair->table, "");
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GHashTable *schema_table;
|
|
GvdbPair root_pair;
|
|
} WriteToFileData;
|
|
|
|
typedef struct
|
|
{
|
|
GHashTable *schema_table;
|
|
GvdbPair pair;
|
|
gboolean l10n;
|
|
} OutputSchemaData;
|
|
|
|
static void
|
|
output_key (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
OutputSchemaData *data;
|
|
const gchar *name;
|
|
KeyState *state;
|
|
GvdbItem *item;
|
|
|
|
name = key;
|
|
state = value;
|
|
data = user_data;
|
|
|
|
item = gvdb_hash_table_insert (data->pair.table, name);
|
|
gvdb_item_set_parent (item, data->pair.root);
|
|
gvdb_item_set_value (item, key_state_serialise (state));
|
|
|
|
if (state->l10n)
|
|
data->l10n = TRUE;
|
|
|
|
if (state->child_schema &&
|
|
!g_hash_table_lookup (data->schema_table, state->child_schema))
|
|
g_printerr ("warning: undefined reference to <schema id='%s'/>\n",
|
|
state->child_schema);
|
|
}
|
|
|
|
static void
|
|
output_schema (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
WriteToFileData *wtf_data = user_data;
|
|
OutputSchemaData data;
|
|
GvdbPair *root_pair;
|
|
SchemaState *state;
|
|
const gchar *id;
|
|
GvdbItem *item;
|
|
|
|
id = key;
|
|
state = value;
|
|
root_pair = &wtf_data->root_pair;
|
|
|
|
data.schema_table = wtf_data->schema_table;
|
|
gvdb_pair_init (&data.pair);
|
|
data.l10n = FALSE;
|
|
|
|
item = gvdb_hash_table_insert (root_pair->table, id);
|
|
gvdb_item_set_parent (item, root_pair->root);
|
|
gvdb_item_set_hash_table (item, data.pair.table);
|
|
|
|
g_hash_table_foreach (state->keys, output_key, &data);
|
|
|
|
if (state->path)
|
|
gvdb_hash_table_insert_string (data.pair.table, ".path", state->path);
|
|
|
|
if (state->extends_name)
|
|
gvdb_hash_table_insert_string (data.pair.table, ".extends",
|
|
state->extends_name);
|
|
|
|
if (state->list_of)
|
|
gvdb_hash_table_insert_string (data.pair.table, ".list-of",
|
|
state->extends_name);
|
|
|
|
if (data.l10n)
|
|
gvdb_hash_table_insert_string (data.pair.table,
|
|
".gettext-domain",
|
|
state->gettext_domain);
|
|
}
|
|
|
|
static gboolean
|
|
write_to_file (GHashTable *schema_table,
|
|
const gchar *filename,
|
|
GError **error)
|
|
{
|
|
WriteToFileData data;
|
|
gboolean success;
|
|
|
|
data.schema_table = schema_table;
|
|
|
|
gvdb_pair_init (&data.root_pair);
|
|
|
|
g_hash_table_foreach (schema_table, output_schema, &data);
|
|
|
|
success = gvdb_table_write_contents (data.root_pair.table, filename,
|
|
G_BYTE_ORDER != G_LITTLE_ENDIAN,
|
|
error);
|
|
g_hash_table_unref (data.root_pair.table);
|
|
|
|
return success;
|
|
}
|
|
|
|
/* Parser driver {{{1 */
|
|
static GHashTable *
|
|
parse_gschema_files (gchar **files,
|
|
gboolean strict)
|
|
{
|
|
GMarkupParser parser = { start_element, end_element, text };
|
|
ParseState state = { 0, };
|
|
const gchar *filename;
|
|
GError *error = NULL;
|
|
|
|
state.enum_table = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, enum_state_free);
|
|
|
|
state.flags_table = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, enum_state_free);
|
|
|
|
state.schema_table = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, schema_state_free);
|
|
|
|
while ((filename = *files++) != NULL)
|
|
{
|
|
GMarkupParseContext *context;
|
|
gchar *contents;
|
|
gsize size;
|
|
|
|
if (!g_file_get_contents (filename, &contents, &size, &error))
|
|
{
|
|
fprintf (stderr, "%s\n", error->message);
|
|
g_clear_error (&error);
|
|
continue;
|
|
}
|
|
|
|
context = g_markup_parse_context_new (&parser,
|
|
G_MARKUP_PREFIX_ERROR_POSITION,
|
|
&state, NULL);
|
|
|
|
|
|
if (!g_markup_parse_context_parse (context, contents, size, &error) ||
|
|
!g_markup_parse_context_end_parse (context, &error))
|
|
{
|
|
GSList *item;
|
|
|
|
/* back out any changes from this file */
|
|
for (item = state.this_file_schemas; item; item = item->next)
|
|
g_hash_table_remove (state.schema_table, item->data);
|
|
|
|
for (item = state.this_file_flagss; item; item = item->next)
|
|
g_hash_table_remove (state.flags_table, item->data);
|
|
|
|
for (item = state.this_file_enums; item; item = item->next)
|
|
g_hash_table_remove (state.enum_table, item->data);
|
|
|
|
/* let them know */
|
|
fprintf (stderr, "%s: %s. ", filename, error->message);
|
|
g_clear_error (&error);
|
|
|
|
if (strict)
|
|
{
|
|
/* Translators: Do not translate "--strict". */
|
|
fprintf (stderr, _("--strict was specified; exiting.\n"));
|
|
g_hash_table_unref (state.schema_table);
|
|
g_hash_table_unref (state.flags_table);
|
|
g_hash_table_unref (state.enum_table);
|
|
|
|
return NULL;
|
|
}
|
|
else
|
|
fprintf (stderr, _("This entire file has been ignored.\n"));
|
|
}
|
|
|
|
/* cleanup */
|
|
g_markup_parse_context_free (context);
|
|
g_slist_free (state.this_file_schemas);
|
|
g_slist_free (state.this_file_flagss);
|
|
g_slist_free (state.this_file_enums);
|
|
state.this_file_schemas = NULL;
|
|
state.this_file_flagss = NULL;
|
|
state.this_file_enums = NULL;
|
|
}
|
|
|
|
g_hash_table_unref (state.flags_table);
|
|
g_hash_table_unref (state.enum_table);
|
|
|
|
return state.schema_table;
|
|
}
|
|
|
|
static gint
|
|
compare_strings (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
gchar *one = *(gchar **) a;
|
|
gchar *two = *(gchar **) b;
|
|
gint cmp;
|
|
|
|
cmp = g_str_has_suffix (two, ".enums.xml") -
|
|
g_str_has_suffix (one, ".enums.xml");
|
|
|
|
if (!cmp)
|
|
cmp = strcmp (one, two);
|
|
|
|
return cmp;
|
|
}
|
|
|
|
static gboolean
|
|
set_overrides (GHashTable *schema_table,
|
|
gchar **files,
|
|
gboolean strict)
|
|
{
|
|
const gchar *filename;
|
|
GError *error = NULL;
|
|
|
|
while ((filename = *files++))
|
|
{
|
|
GKeyFile *key_file;
|
|
gchar **groups;
|
|
gint i;
|
|
|
|
key_file = g_key_file_new ();
|
|
if (!g_key_file_load_from_file (key_file, filename, 0, &error))
|
|
{
|
|
fprintf (stderr, "%s: %s. ", filename, error->message);
|
|
g_key_file_free (key_file);
|
|
g_clear_error (&error);
|
|
|
|
if (!strict)
|
|
{
|
|
fprintf (stderr, _("Ignoring this file.\n"));
|
|
continue;
|
|
}
|
|
|
|
fprintf (stderr, _("--strict was specified; exiting.\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
groups = g_key_file_get_groups (key_file, NULL);
|
|
|
|
for (i = 0; groups[i]; i++)
|
|
{
|
|
const gchar *group = groups[i];
|
|
SchemaState *schema;
|
|
gchar **keys;
|
|
gint j;
|
|
|
|
schema = g_hash_table_lookup (schema_table, group);
|
|
|
|
if (schema == NULL)
|
|
/* Having the schema not be installed is expected to be a
|
|
* common case. Don't even emit an error message about
|
|
* that.
|
|
*/
|
|
continue;
|
|
|
|
keys = g_key_file_get_keys (key_file, group, NULL, NULL);
|
|
g_assert (keys != NULL);
|
|
|
|
for (j = 0; keys[j]; j++)
|
|
{
|
|
const gchar *key = keys[j];
|
|
KeyState *state;
|
|
GVariant *value;
|
|
gchar *string;
|
|
|
|
state = g_hash_table_lookup (schema->keys, key);
|
|
|
|
if (state == NULL)
|
|
{
|
|
fprintf (stderr, _("No such key `%s' in schema `%s' as "
|
|
"specified in override file `%s'"),
|
|
key, group, filename);
|
|
|
|
if (!strict)
|
|
{
|
|
fprintf (stderr, _("; ignoring override for this key.\n"));
|
|
continue;
|
|
}
|
|
|
|
fprintf (stderr, _(" and --strict was specified; exiting.\n"));
|
|
g_key_file_free (key_file);
|
|
g_strfreev (groups);
|
|
g_strfreev (keys);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
string = g_key_file_get_value (key_file, group, key, NULL);
|
|
g_assert (string != NULL);
|
|
|
|
value = g_variant_parse (state->type, string,
|
|
NULL, NULL, &error);
|
|
|
|
if (value == NULL)
|
|
{
|
|
fprintf (stderr, _("error parsing key `%s' in schema `%s' "
|
|
"as specified in override file `%s': "
|
|
"%s. "),
|
|
key, group, filename, error->message);
|
|
|
|
g_clear_error (&error);
|
|
g_free (string);
|
|
|
|
if (!strict)
|
|
{
|
|
fprintf (stderr, _("Ignoring override for this key.\n"));
|
|
continue;
|
|
}
|
|
|
|
fprintf (stderr, _("--strict was specified; exiting.\n"));
|
|
g_key_file_free (key_file);
|
|
g_strfreev (groups);
|
|
g_strfreev (keys);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (state->minimum)
|
|
{
|
|
if (g_variant_compare (value, state->minimum) < 0 ||
|
|
g_variant_compare (value, state->maximum) > 0)
|
|
{
|
|
fprintf (stderr,
|
|
_("override for key `%s' in schema `%s' in "
|
|
"override file `%s' is out of the range "
|
|
"given in the schema"),
|
|
key, group, filename);
|
|
|
|
g_variant_unref (value);
|
|
g_free (string);
|
|
|
|
if (!strict)
|
|
{
|
|
fprintf (stderr, _("; ignoring override for this key.\n"));
|
|
continue;
|
|
}
|
|
|
|
fprintf (stderr, _(" and --strict was specified; exiting.\n"));
|
|
g_key_file_free (key_file);
|
|
g_strfreev (groups);
|
|
g_strfreev (keys);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
else if (state->strinfo->len)
|
|
{
|
|
if (!is_valid_choices (value, state->strinfo))
|
|
{
|
|
fprintf (stderr,
|
|
_("override for key `%s' in schema `%s' in "
|
|
"override file `%s' is not in the list "
|
|
"of valid choices"),
|
|
key, group, filename);
|
|
|
|
g_variant_unref (value);
|
|
g_free (string);
|
|
|
|
if (!strict)
|
|
{
|
|
fprintf (stderr, _("; ignoring override for this key.\n"));
|
|
continue;
|
|
}
|
|
|
|
fprintf (stderr, _(" and --strict was specified; exiting.\n"));
|
|
g_key_file_free (key_file);
|
|
g_strfreev (groups);
|
|
g_strfreev (keys);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
g_variant_unref (state->default_value);
|
|
state->default_value = value;
|
|
g_free (string);
|
|
}
|
|
|
|
g_strfreev (keys);
|
|
}
|
|
|
|
g_strfreev (groups);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
GError *error;
|
|
GHashTable *table;
|
|
GDir *dir;
|
|
const gchar *file;
|
|
gchar *srcdir;
|
|
gchar *targetdir = NULL;
|
|
gchar *target;
|
|
gboolean uninstall = FALSE;
|
|
gboolean dry_run = FALSE;
|
|
gboolean strict = FALSE;
|
|
gchar **schema_files = NULL;
|
|
gchar **override_files = NULL;
|
|
GOptionContext *context;
|
|
GOptionEntry entries[] = {
|
|
{ "targetdir", 0, 0, G_OPTION_ARG_FILENAME, &targetdir, N_("where to store the gschemas.compiled file"), N_("DIRECTORY") },
|
|
{ "strict", 0, 0, G_OPTION_ARG_NONE, &strict, N_("Abort on any errors in schemas"), NULL },
|
|
{ "dry-run", 0, 0, G_OPTION_ARG_NONE, &dry_run, N_("Do not write the gschema.compiled file"), NULL },
|
|
{ "uninstall", 0, 0, G_OPTION_ARG_NONE, &uninstall, N_("This option will be removed soon.") },
|
|
{ "allow-any-name", 0, 0, G_OPTION_ARG_NONE, &allow_any_name, N_("Do not enforce key name restrictions") },
|
|
|
|
/* These options are only for use in the gschema-compile tests */
|
|
{ "schema-file", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME_ARRAY, &schema_files, NULL, NULL },
|
|
{ NULL }
|
|
};
|
|
|
|
setlocale (LC_ALL, "");
|
|
textdomain (GETTEXT_PACKAGE);
|
|
#ifdef G_OS_WIN32
|
|
extern gchar *_glib_get_locale_dir (void);
|
|
gchar *tmp = _glib_get_locale_dir ();
|
|
bindtextdomain (GETTEXT_PACKAGE, tmp);
|
|
g_free (tmp);
|
|
#else
|
|
bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
|
|
#endif
|
|
|
|
#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
|
|
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
|
|
#endif
|
|
|
|
context = g_option_context_new (N_("DIRECTORY"));
|
|
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
|
|
g_option_context_set_summary (context,
|
|
N_("Compile all GSettings schema files into a schema cache.\n"
|
|
"Schema files are required to have the extension .gschema.xml,\n"
|
|
"and the cache file is called gschemas.compiled."));
|
|
g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
|
|
|
|
error = NULL;
|
|
if (!g_option_context_parse (context, &argc, &argv, &error))
|
|
{
|
|
fprintf (stderr, "%s\n", error->message);
|
|
return 1;
|
|
}
|
|
|
|
g_option_context_free (context);
|
|
|
|
if (!schema_files && argc != 2)
|
|
{
|
|
fprintf (stderr, _("You should give exactly one directory name\n"));
|
|
return 1;
|
|
}
|
|
|
|
srcdir = argv[1];
|
|
|
|
if (targetdir == NULL)
|
|
targetdir = srcdir;
|
|
|
|
target = g_build_filename (targetdir, "gschemas.compiled", NULL);
|
|
|
|
if (!schema_files)
|
|
{
|
|
GPtrArray *overrides;
|
|
GPtrArray *files;
|
|
|
|
files = g_ptr_array_new ();
|
|
overrides = g_ptr_array_new ();
|
|
|
|
dir = g_dir_open (srcdir, 0, &error);
|
|
if (dir == NULL)
|
|
{
|
|
fprintf (stderr, "%s\n", error->message);
|
|
return 1;
|
|
}
|
|
|
|
while ((file = g_dir_read_name (dir)) != NULL)
|
|
{
|
|
if (g_str_has_suffix (file, ".gschema.xml") ||
|
|
g_str_has_suffix (file, ".enums.xml"))
|
|
g_ptr_array_add (files, g_build_filename (srcdir, file, NULL));
|
|
|
|
else if (g_str_has_suffix (file, ".gschema.override"))
|
|
g_ptr_array_add (overrides,
|
|
g_build_filename (srcdir, file, NULL));
|
|
}
|
|
|
|
if (files->len == 0)
|
|
{
|
|
fprintf (stderr, _("No schema files found: "));
|
|
|
|
if (g_unlink (target))
|
|
fprintf (stderr, _("doing nothing.\n"));
|
|
|
|
else
|
|
fprintf (stderr, _("removed existing output file.\n"));
|
|
|
|
return 0;
|
|
}
|
|
g_ptr_array_sort (files, compare_strings);
|
|
g_ptr_array_add (files, NULL);
|
|
|
|
g_ptr_array_sort (overrides, compare_strings);
|
|
g_ptr_array_add (overrides, NULL);
|
|
|
|
schema_files = (char **) g_ptr_array_free (files, FALSE);
|
|
override_files = (gchar **) g_ptr_array_free (overrides, FALSE);
|
|
}
|
|
|
|
if ((table = parse_gschema_files (schema_files, strict)) == NULL)
|
|
{
|
|
g_free (target);
|
|
return 1;
|
|
}
|
|
|
|
if (override_files != NULL &&
|
|
!set_overrides (table, override_files, strict))
|
|
{
|
|
g_free (target);
|
|
return 1;
|
|
}
|
|
|
|
if (!dry_run && !write_to_file (table, target, &error))
|
|
{
|
|
fprintf (stderr, "%s\n", error->message);
|
|
g_free (target);
|
|
return 1;
|
|
}
|
|
|
|
g_free (target);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Epilogue {{{1 */
|
|
|
|
/* vim:set foldmethod=marker: */
|