diff --git a/gio/Makefile.am b/gio/Makefile.am index bbf57f113..6f58683ce 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -389,6 +389,7 @@ libgio_2_0_la_SOURCES = \ giostream.c \ gkeyfileencoder.c \ gloadableicon.c \ + gmarkupencoder.c \ gmount.c \ gmemoryinputstream.c \ gmemoryoutputstream.c \ @@ -565,6 +566,7 @@ gio_headers = \ giostream.h \ gkeyfileencoder.h \ gloadableicon.h \ + gmarkupencoder.h \ gmount.h \ gmemoryinputstream.h \ gmemoryoutputstream.h \ diff --git a/gio/gio.h b/gio/gio.h index ac4d77a56..1f345bfd1 100644 --- a/gio/gio.h +++ b/gio/gio.h @@ -90,6 +90,7 @@ #include #include #include +#include #include #include #include diff --git a/gio/giotypes.h b/gio/giotypes.h index 1b4c2b2fc..f9c35cfd0 100644 --- a/gio/giotypes.h +++ b/gio/giotypes.h @@ -50,6 +50,7 @@ typedef struct _GEncoder GEncoder; typedef struct _GSerializable GSerializable; typedef struct _GBinaryEncoder GBinaryEncoder; typedef struct _GKeyfileEncoder GKeyfileEncoder; +typedef struct _GMarkupEncoder GMarkupEncoder; typedef struct _GSimpleActionGroup GSimpleActionGroup; typedef struct _GRemoteActionGroup GRemoteActionGroup; diff --git a/gio/gmarkupencoder.c b/gio/gmarkupencoder.c new file mode 100644 index 000000000..9da234887 --- /dev/null +++ b/gio/gmarkupencoder.c @@ -0,0 +1,437 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2013 Emmanuele Bassi + * + * 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 License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gmarkupencoder + * @Title: GMarkupEncoder + * @Short_Description: Encodes and decodes data as markup + * + * #GMarkupEncoder is a class that allows encoding and decoding data + * as an XML subset that can be parsed by #GMarkupParser. + * + * Note that you can only use #GMarkupEncoder to decode the output + * of data encoded by a #GMarkupEncoder. + */ + +#include "config.h" + +#include "gmarkupencoder.h" +#include "gencoder.h" +#include "gioerror.h" +#include "glibintl.h" + +#include + +struct _GMarkupEncoder +{ + GEncoder parent_instance; + + /* parser state */ + char *cur_key; + char *cur_value; + char *cur_value_type; + + guint in_entries : 1; + guint in_entry : 1; + guint in_key : 1; + guint in_value : 1; +}; + +struct _GMarkupEncoderClass +{ + GEncoderClass parent_class; +}; + +G_DEFINE_TYPE (GMarkupEncoder, g_markup_encoder, G_TYPE_ENCODER) + +static void +g_markup_encoder_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + GMarkupEncoder *self = user_data; + + if (strcmp (element_name, "entries") == 0) + { + if (self->in_entries) + { + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "The 'entries' tag cannot be nested"); + return; + } + + g_assert (!self->in_entry); + g_assert (!self->in_key); + g_assert (!self->in_value); + self->in_entries = TRUE; + + return; + } + + if (strcmp (element_name, "entry") == 0) + { + if (!self->in_entries) + { + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "The 'entry' tag can only be used inside an 'entries' tag"); + return; + } + + g_assert (!self->in_entry); + g_assert (!self->in_key); + g_assert (!self->in_value); + self->in_entry = TRUE; + + g_free (self->cur_key); + self->cur_key = NULL; + g_free (self->cur_value_type); + self->cur_value_type = NULL; + g_free (self->cur_value); + self->cur_value = NULL; + return; + } + + if (strcmp (element_name, "key") == 0) + { + if (!self->in_entry) + { + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "The 'key' tag can only be used inside an 'entry' tag"); + return; + } + + g_assert (!self->in_value); + self->in_key = TRUE; + return; + } + + if (strcmp (element_name, "value") == 0) + { + gboolean res; + + if (!self->in_entry) + { + g_set_error_literal (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "The 'value' tag can only be used inside an 'entry' tag"); + return; + } + + res = g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_STRDUP, "type", &self->cur_value_type, + G_MARKUP_COLLECT_INVALID); + if (!res) + return; + + g_assert (!self->in_key); + self->in_value = TRUE; + return; + } +} + +static void +g_markup_encoder_add_current_entry (GMarkupEncoder *self, + GError **error) +{ + GError *internal_error; + GVariant *variant; + + if (self->cur_key == NULL) + { + if (self->cur_value_type != NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "No key defined for entry of type '%s'", + self->cur_value_type); + } + else + { + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "No key defined for entry"); + } + + return; + } + + if (self->cur_value_type == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "No value type defined for key '%s'", + self->cur_key); + return; + } + + if (self->cur_value == NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "No value defined for key '%s' of type '%s'", + self->cur_key, + self->cur_value_type); + return; + } + + internal_error = NULL; + variant = g_variant_parse (G_VARIANT_TYPE (self->cur_value_type), + self->cur_value, + NULL, + NULL, + &internal_error); + if (internal_error != NULL) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Unable to parse the entry value: %s", + internal_error->message); + g_error_free (internal_error); + return; + } + + g_encoder_add_key (G_ENCODER (self), self->cur_key, variant); + g_variant_unref (variant); +} + +static void +g_markup_encoder_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + GMarkupEncoder *self = user_data; + + if (strcmp (element_name, "entries") == 0) + { + self->in_entries = FALSE; + return; + } + + if (strcmp (element_name, "entry") == 0) + { + g_assert (self->in_entries); + self->in_entry = FALSE; + + g_markup_encoder_add_current_entry (self, error); + + g_free (self->cur_key); + self->cur_key = NULL; + g_free (self->cur_value_type); + self->cur_value_type = NULL; + g_free (self->cur_value); + self->cur_value = NULL; + return; + } + + if (strcmp (element_name, "key") == 0) + { + g_assert (self->in_entries); + g_assert (self->in_entry); + self->in_key = FALSE; + return; + } + + if (strcmp (element_name, "value") == 0) + { + g_assert (self->in_entries); + g_assert (self->in_entry); + self->in_value = FALSE; + return; + } + + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Unknown element '%s' in markup", + element_name); +} + +static void +g_markup_encoder_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + GMarkupEncoder *self = user_data; + + if (self->in_key) + { + g_free (self->cur_key); + self->cur_key = g_strndup (text, text_len); + return; + } + + if (self->in_value) + { + g_free (self->cur_value); + self->cur_value = g_strndup (text, text_len); + return; + } +} + +static const GMarkupParser markup_parser = { + /* .start_element = */ g_markup_encoder_start_element, + /* .end_element = */ g_markup_encoder_end_element, + /* .text = */ g_markup_encoder_text, + /* .passthrough = */ NULL, + /* .error = */ NULL, +}; + +static void +clear_parser_state (gpointer data) +{ + GMarkupEncoder *self = data; + + g_free (self->cur_key); + self->cur_key = NULL; + g_free (self->cur_value_type); + self->cur_value_type = NULL; + g_free (self->cur_value); + self->cur_value = NULL; + + self->in_entries = FALSE; + self->in_entry = FALSE; + self->in_key = FALSE; + self->in_value = FALSE; +} + +static gboolean +g_markup_encoder_read_from_bytes (GEncoder *encoder, + GBytes *buffer, + GError **error) +{ + GMarkupParseContext *context; + GError *internal_error = NULL; + + clear_parser_state (encoder); + + context = g_markup_parse_context_new (&markup_parser, 0, encoder, clear_parser_state); + + g_markup_parse_context_parse (context, + g_bytes_get_data (buffer, NULL), + g_bytes_get_size (buffer), + &internal_error); + + g_markup_parse_context_free (context); + + if (internal_error) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Unable to read markup data: %s", + internal_error->message); + g_error_free (internal_error); + + return FALSE; + } + + return TRUE; +} + +static GBytes * +g_markup_encoder_write_to_bytes (GEncoder *encoder, + GError **error) +{ + GVariant *data = g_encoder_close (encoder); + GVariant *entry; + GVariantIter iter; + GString *buffer; + gsize len; + + buffer = g_string_sized_new (1024); + + g_string_append (buffer, "\n"); + g_string_append (buffer, "\n"); + + g_variant_iter_init (&iter, data); + while ((entry = g_variant_iter_next_value (&iter)) != NULL) + { + GVariant *key = g_variant_get_child_value (entry, 0); + GVariant *tmp = g_variant_get_child_value (entry, 1); + GVariant *value; + char *value_str; + + g_string_append (buffer, " \n"); + + value = g_variant_get_variant (tmp); + value_str = g_variant_print (value, FALSE); + + g_string_append_printf (buffer, + " %s\n", + g_variant_get_string (key, NULL)); + + g_string_append_printf (buffer, + " %s\n", + (const char *) g_variant_get_type (value), + value_str); + + g_free (value_str); + g_variant_unref (value); + g_variant_unref (tmp); + g_variant_unref (key); + + g_string_append (buffer, " \n"); + } + + g_string_append (buffer, ""); + len = buffer->len; + + return g_bytes_new_take (g_string_free (buffer, FALSE), len); +} + +static void +g_markup_encoder_class_init (GMarkupEncoderClass *klass) +{ + GEncoderClass *encoder_class = G_ENCODER_CLASS (klass); + + encoder_class->read_from_bytes = g_markup_encoder_read_from_bytes; + encoder_class->write_to_bytes = g_markup_encoder_write_to_bytes; +} + +static void +g_markup_encoder_init (GMarkupEncoder *self) +{ +} + +/** + * g_markup_encoder_new: + * + * Creates a new #GMarkupEncoder instance. + * + * Returns: (transfer full): the newly created #GMarkupEncoder instance. + * Use g_object_unref() when done. + * + * Since: 2.38 + */ +GEncoder * +g_markup_encoder_new (void) +{ + return g_object_new (G_TYPE_MARKUP_ENCODER, NULL); +} diff --git a/gio/gmarkupencoder.h b/gio/gmarkupencoder.h new file mode 100644 index 000000000..2c723bb97 --- /dev/null +++ b/gio/gmarkupencoder.h @@ -0,0 +1,49 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2013 Emmanuele Bassi + * + * 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 License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __G_MARKUP_ENCODER_H__ +#define __G_MARKUP_ENCODER_H__ + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define G_TYPE_MARKUP_ENCODER (g_markup_encoder_get_type ()) +#define G_MARKUP_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_MARKUP_ENCODER, GMarkupEncoder)) +#define G_IS_MARKUP_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_MARKUP_ENCODER)) +#define G_MARKUP_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_MARKUP_ENCODER, GMarkupEncoderClass)) +#define G_IS_MARKUP_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_MARKUP_ENCODER)) +#define G_MARKUP_ENCODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_MARKUP_ENCODER, GMarkupEncoderClass)) + +typedef struct _GMarkupEncoderClass GMarkupEncoderClass; + +GLIB_AVAILABLE_IN_2_38 +GType g_markup_encoder_get_type (void) G_GNUC_CONST; + +GLIB_AVAILABLE_IN_2_38 +GEncoder * g_markup_encoder_new (void); + +G_END_DECLS + +#endif /* __G_MARKUP_ENCODER_H__ */ diff --git a/gio/tests/encoder.c b/gio/tests/encoder.c index 698f23bb7..04540115f 100644 --- a/gio/tests/encoder.c +++ b/gio/tests/encoder.c @@ -1,34 +1,35 @@ #include -static void -encoder_binary (void) +static GBytes * +encode_data (GEncoder *encoder) { - GEncoder *encoder = g_binary_encoder_new (); GError *error = NULL; GBytes *buffer; - gboolean bool_value; - char *str_value; - - g_object_add_weak_pointer (G_OBJECT (encoder), (gpointer *) &encoder); g_encoder_add_key_bool (encoder, "BoolValue", TRUE); g_encoder_add_key_string (encoder, "StringValue", "Hello"); + g_encoder_add_key_double (encoder, "DoubleValue", 3.14159); buffer = g_encoder_write_to_bytes (encoder, &error); g_assert_no_error (error); g_assert (buffer != NULL); - g_object_unref (encoder); - g_assert (encoder == NULL); - if (g_test_verbose ()) g_print ("*** buffer (len: %d) = ***\n%s\n", (int) g_bytes_get_size (buffer), (const char *) g_bytes_get_data (buffer, NULL)); - encoder = g_binary_encoder_new (); + return buffer; +} - g_object_add_weak_pointer (G_OBJECT (encoder), (gpointer *) &encoder); +static void +decode_data (GEncoder *encoder, + GBytes *buffer) +{ + GError *error = NULL; + gboolean bool_value; + char *str_value; + double dbl_value; g_encoder_read_from_bytes (encoder, buffer, &error); g_assert_no_error (error); @@ -40,45 +41,58 @@ encoder_binary (void) g_assert_cmpstr (str_value, ==, "Hello"); g_free (str_value); - g_bytes_unref (buffer); + g_encoder_get_key_double (encoder, "DoubleValue", &dbl_value); + g_assert_cmpfloat ((float) dbl_value, ==, 3.14159f); +} +static void +encoder_binary (void) +{ + GEncoder *encoder, *decoder; + GBytes *buffer; + + encoder = g_binary_encoder_new (); + buffer = encode_data (encoder); g_object_unref (encoder); - g_assert (encoder == NULL); + + decoder = g_binary_encoder_new (); + decode_data (decoder, buffer); + g_object_unref (decoder); + g_bytes_unref (buffer); } static void encoder_keyfile (void) { - GEncoder *encoder = g_keyfile_encoder_new (); - GError *error = NULL; + GEncoder *encoder, *decoder; GBytes *buffer; - gboolean res; - - g_keyfile_encoder_set_section_name (G_KEYFILE_ENCODER (encoder), "Test"); - g_encoder_add_key_bool (encoder, "BoolValue", TRUE); - - buffer = g_encoder_write_to_bytes (encoder, &error); - g_assert_no_error (error); - g_assert (buffer != NULL); - - g_object_unref (encoder); - - if (g_test_verbose ()) - g_print ("*** buffer (len: %d) = ***\n%s", - (int) g_bytes_get_size (buffer), - (const char *) g_bytes_get_data (buffer, NULL)); encoder = g_keyfile_encoder_new (); g_keyfile_encoder_set_section_name (G_KEYFILE_ENCODER (encoder), "Test"); - - g_encoder_read_from_bytes (encoder, buffer, &error); - g_assert_no_error (error); - - g_encoder_get_key_bool (encoder, "BoolValue", &res); - g_assert (res); - - g_bytes_unref (buffer); + buffer = encode_data (encoder); g_object_unref (encoder); + + decoder = g_keyfile_encoder_new (); + g_keyfile_encoder_set_section_name (G_KEYFILE_ENCODER (decoder), "Test"); + decode_data (decoder, buffer); + g_object_unref (decoder); + g_bytes_unref (buffer); +} + +static void +encoder_markup (void) +{ + GEncoder *encoder, *decoder; + GBytes *buffer; + + encoder = g_markup_encoder_new (); + buffer = encode_data (encoder); + g_object_unref (encoder); + + decoder = g_markup_encoder_new (); + decode_data (decoder, buffer); + g_object_unref (decoder); + g_bytes_unref (buffer); } int @@ -88,6 +102,7 @@ main (int argc, char *argv[]) g_test_add_func ("/encoder/binary", encoder_binary); g_test_add_func ("/encoder/key-file", encoder_keyfile); + g_test_add_func ("/encoder/markup", encoder_markup); return g_test_run (); }