[libgxps] Add support for resource dictionaries



commit 29ddc509877a684c4dfb692805eb2e420a50f20c
Author: Ignacio Casal Quinteiro <ignacio casal nice-software com>
Date:   Thu Jan 26 12:57:47 2017 +0100

    Add support for resource dictionaries
    
    It parses the resources and keeps a cache in the form of xml.
    Once we are parsing the real xml if we find a resource we
    get the cached xml resource and we parse it to properly handle it.
    
    For now only Path Data resources are supported.
    
    Based in a patch from Jason Crain.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=777731

 libgxps/Makefile.am         |    2 +-
 libgxps/Makefile.sources    |    2 +
 libgxps/gxps-archive.c      |   16 ++
 libgxps/gxps-archive.h      |    2 +
 libgxps/gxps-page-private.h |    1 +
 libgxps/gxps-page.c         |  137 +++++++++++-
 libgxps/gxps-resources.c    |  523 +++++++++++++++++++++++++++++++++++++++++++
 libgxps/gxps-resources.h    |   50 ++++
 8 files changed, 729 insertions(+), 4 deletions(-)
---
diff --git a/libgxps/Makefile.am b/libgxps/Makefile.am
index bdff76a..f2f16ab 100644
--- a/libgxps/Makefile.am
+++ b/libgxps/Makefile.am
@@ -55,7 +55,7 @@ INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --warn-all --identifie
 INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir)
 
 if HAVE_INTROSPECTION
-introspection_sources = $(filter-out $(GXPS_BASE_NOINST_H_FILES) gxps-archive.c gxps-fonts.c gxps-images.c 
gxps-parse-utils.c gxps-version.h, $(libgxps_la_SOURCES))
+introspection_sources = $(filter-out $(GXPS_BASE_NOINST_H_FILES) gxps-resources.c gxps-archive.c 
gxps-fonts.c gxps-images.c gxps-parse-utils.c gxps-version.h, $(libgxps_la_SOURCES))
 
 GXPS-0.1.gir: libgxps.la
 GXPS_0_1_gir_INCLUDES = GObject-2.0 Gio-2.0 cairo-1.0
diff --git a/libgxps/Makefile.sources b/libgxps/Makefile.sources
index 9d6a24a..c9adc55 100644
--- a/libgxps/Makefile.sources
+++ b/libgxps/Makefile.sources
@@ -13,6 +13,7 @@ GXPS_BASE_NOINST_H_FILES = \
        gxps-parse-utils.h      \
        gxps-path.h             \
        gxps-private.h          \
+       gxps-resources.h        \
        $(NULL)
 
 GXPS_BASE_INST_H_FILES = \
@@ -45,4 +46,5 @@ GXPS_BASE_SOURCES = \
        gxps-page.c                     \
        gxps-parse-utils.c              \
        gxps-path.c                     \
+       gxps-resources.c                \
        $(NULL)
diff --git a/libgxps/gxps-archive.c b/libgxps/gxps-archive.c
index c3f2d2c..398320a 100644
--- a/libgxps/gxps-archive.c
+++ b/libgxps/gxps-archive.c
@@ -39,6 +39,8 @@ struct _GXPSArchive {
        GError     *init_error;
        GFile      *filename;
        GHashTable *entries;
+
+       GXPSResources *resources;
 };
 
 struct _GXPSArchiveClass {
@@ -182,6 +184,7 @@ gxps_archive_finalize (GObject *object)
        g_clear_pointer (&archive->entries, g_hash_table_unref);
        g_clear_object (&archive->filename);
        g_clear_error (&archive->init_error);
+       g_clear_object (&archive->resources);
 
        G_OBJECT_CLASS (gxps_archive_parent_class)->finalize (object);
 }
@@ -318,6 +321,19 @@ gxps_archive_has_entry (GXPSArchive *archive,
        return g_hash_table_contains (archive->entries, path);
 }
 
+GXPSResources *
+gxps_archive_get_resources (GXPSArchive *archive)
+{
+       g_return_val_if_fail (GXPS_IS_ARCHIVE (archive), NULL);
+
+       if (archive->resources == NULL)
+               archive->resources = g_object_new (GXPS_TYPE_RESOURCES,
+                                                  "archive", archive,
+                                                  NULL);
+
+       return archive->resources;
+}
+
 /* GXPSArchiveInputStream */
 typedef struct _GXPSArchiveInputStream {
        GInputStream          parent;
diff --git a/libgxps/gxps-archive.h b/libgxps/gxps-archive.h
index 4029fa1..a5054e1 100644
--- a/libgxps/gxps-archive.h
+++ b/libgxps/gxps-archive.h
@@ -24,6 +24,7 @@
 #include <gio/gio.h>
 #include <archive.h>
 #include <libgxps/gxps-version.h>
+#include <libgxps/gxps-resources.h>
 
 G_BEGIN_DECLS
 
@@ -42,6 +43,7 @@ GXPSArchive      *gxps_archive_new            (GFile            *filename,
                                               GError          **error);
 gboolean          gxps_archive_has_entry      (GXPSArchive      *archive,
                                               const gchar      *path);
+GXPSResources    *gxps_archive_get_resources  (GXPSArchive      *archive);
 GInputStream     *gxps_archive_open           (GXPSArchive      *archive,
                                               const gchar      *path);
 gboolean          gxps_archive_read_entry     (GXPSArchive      *archive,
diff --git a/libgxps/gxps-page-private.h b/libgxps/gxps-page-private.h
index 9dab52c..7bcdddb 100644
--- a/libgxps/gxps-page-private.h
+++ b/libgxps/gxps-page-private.h
@@ -26,6 +26,7 @@
 #include "gxps-page.h"
 #include "gxps-archive.h"
 #include "gxps-images.h"
+#include "gxps-resources.h"
 
 G_BEGIN_DECLS
 
diff --git a/libgxps/gxps-page.c b/libgxps/gxps-page.c
index 8baa10b..40145b8 100644
--- a/libgxps/gxps-page.c
+++ b/libgxps/gxps-page.c
@@ -276,6 +276,7 @@ typedef struct {
 
        gdouble            opacity;
        cairo_pattern_t   *opacity_mask;
+       gboolean           pop_resource_dict;
 } GXPSCanvas;
 
 static GXPSCanvas *
@@ -288,6 +289,7 @@ gxps_canvas_new (GXPSRenderContext *ctx)
 
        /* Default values */
        canvas->opacity = 1.0;
+       canvas->pop_resource_dict = FALSE;
 
        return canvas;
 }
@@ -322,6 +324,22 @@ canvas_start_element (GMarkupParseContext  *context,
 
                brush = gxps_brush_new (canvas->ctx);
                gxps_brush_parser_push (context, brush);
+       } else if (strcmp (element_name, "Canvas.Resources") == 0) {
+               GXPSResources *resources;
+
+               if (canvas->pop_resource_dict) {
+                       gxps_parse_error (context,
+                                         canvas->ctx->page->priv->source,
+                                         G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                                         element_name, NULL, NULL, error);
+                       return;
+               }
+
+               resources = gxps_archive_get_resources (canvas->ctx->page->priv->zip);
+               gxps_resources_push_dict (resources);
+               canvas->pop_resource_dict = TRUE;
+               gxps_resources_parser_push (context, resources,
+                                           canvas->ctx->page->priv->source);
        } else {
                render_start_element (context,
                                      element_name,
@@ -359,6 +377,8 @@ canvas_end_element (GMarkupParseContext  *context,
                        cairo_push_group (canvas->ctx->cr);
                }
                gxps_brush_free (brush);
+       } else if (strcmp (element_name, "Canvas.Resources") == 0) {
+               gxps_resources_parser_pop (context);
        } else {
                render_end_element (context,
                                    element_name,
@@ -385,6 +405,95 @@ static GMarkupParser canvas_parser = {
 };
 
 static void
+resource_start_element (GMarkupParseContext  *context,
+                        const gchar          *element_name,
+                        const gchar         **names,
+                        const gchar         **values,
+                        gpointer              user_data,
+                        GError              **error)
+{
+       if (strcmp (element_name, "PathGeometry") == 0) {
+               GXPSPath *path = (GXPSPath *)user_data;
+
+               gxps_path_parser_push (context, path);
+       } else if (g_str_has_suffix (element_name, "Brush")) {
+               GXPSPath *path = (GXPSPath *)user_data;
+               GXPSBrush *brush;
+
+               brush = gxps_brush_new (path->ctx);
+               gxps_brush_parser_push (context, brush);
+       }
+}
+
+static void
+resource_end_element (GMarkupParseContext  *context,
+                     const gchar          *element_name,
+                     gpointer              user_data,
+                     GError              **error)
+{
+       if (strcmp (element_name, "PathGeometry") == 0) {
+               g_markup_parse_context_pop (context);
+       } else if (g_str_has_suffix (element_name, "Brush")) {
+               GXPSPath *path = (GXPSPath *)user_data;
+               GXPSBrush *brush = g_markup_parse_context_pop (context);
+
+               path->fill_pattern = cairo_pattern_reference (brush->pattern);
+               gxps_brush_free (brush);
+       }
+}
+
+static GMarkupParser resource_parser = {
+       resource_start_element,
+       resource_end_element,
+       NULL,
+       NULL,
+       NULL
+};
+
+static gboolean
+expand_resource (GXPSPage    *page,
+                 const gchar *data,
+                 gpointer     user_data)
+{
+       gchar *resource_key;
+       gchar *p;
+       gsize len;
+       GXPSResources *resources;
+       const gchar *resource;
+       GMarkupParseContext *context;
+       gboolean ret = TRUE;
+
+       if (!g_str_has_prefix (data, "{StaticResource "))
+               return FALSE;
+
+       p = strstr (data, "}");
+       if (p == NULL)
+               return FALSE;
+
+       len = strlen ("{StaticResource ");
+       resource_key = g_strndup (data + len, p - (data + len));
+
+       if (!resource_key || *resource_key == '\0') {
+               g_free (resource_key);
+               return FALSE;
+       }
+
+       resources = gxps_archive_get_resources (page->priv->zip);
+       resource = gxps_resources_get_resource (resources, resource_key);
+       g_free (resource_key);
+       if (!resource)
+               return FALSE;
+
+       context = g_markup_parse_context_new (&resource_parser, 0, user_data, NULL);
+
+       ret = g_markup_parse_context_parse (context, resource, strlen (resource), NULL) &&
+             g_markup_parse_context_end_parse (context, NULL);
+       g_markup_parse_context_free (context);
+
+       return ret;
+}
+
+static void
 render_start_element (GMarkupParseContext  *context,
                      const gchar          *element_name,
                      const gchar         **names,
@@ -404,7 +513,15 @@ render_start_element (GMarkupParseContext  *context,
                path = gxps_path_new (ctx);
 
                for (i = 0; names[i] != NULL; i++) {
-                       if (strcmp (names[i], "Data") == 0) {
+                       /* FIXME: if the resource gets expanded, that specific
+                        * resource will be already handled leading to a different
+                        * behavior of what we are actually doing without resources.
+                        * In an ideal world we would handle the resource without
+                        * special casing
+                        */
+                       if (expand_resource (ctx->page, values[i], path)) {
+                               GXPS_DEBUG (g_message ("expanded resource: %s", names[i]));
+                       } else if (strcmp (names[i], "Data") == 0) {
                                path->data = g_strdup (values[i]);
                        } else if (strcmp (names[i], "RenderTransform") == 0) {
                                cairo_matrix_t matrix;
@@ -425,8 +542,7 @@ render_start_element (GMarkupParseContext  *context,
                        } else if (strcmp (names[i], "Clip") == 0) {
                                path->clip_data = g_strdup (values[i]);
                        } else if (strcmp (names[i], "Fill") == 0) {
-                               GXPS_DEBUG (g_message ("set_fill_pattern (solid)"));
-                                if (!gxps_brush_solid_color_parse (values[i], ctx->page->priv->zip, 1., 
&path->fill_pattern)) {
+                               if (!gxps_brush_solid_color_parse (values[i], ctx->page->priv->zip, 1., 
&path->fill_pattern)) {
                                        gxps_parse_error (context,
                                                          ctx->page->priv->source,
                                                          G_MARKUP_ERROR_INVALID_CONTENT,
@@ -434,6 +550,7 @@ render_start_element (GMarkupParseContext  *context,
                                        gxps_path_free (path);
                                        return;
                                }
+                               GXPS_DEBUG (g_message ("set_fill_pattern (solid)"));
                        } else if (strcmp (names[i], "Stroke") == 0) {
                                GXPS_DEBUG (g_message ("set_stroke_pattern (solid)"));
                                 if (!gxps_brush_solid_color_parse (values[i], ctx->page->priv->zip, 1., 
&path->stroke_pattern)) {
@@ -713,6 +830,12 @@ render_start_element (GMarkupParseContext  *context,
                if (canvas->opacity != 1.0)
                        cairo_push_group (canvas->ctx->cr);
                g_markup_parse_context_push (context, &canvas_parser, canvas);
+       } else if (strcmp (element_name, "FixedPage.Resources") == 0) {
+               GXPSResources *resources;
+
+               resources = gxps_archive_get_resources (ctx->page->priv->zip);
+               gxps_resources_parser_push (context, resources,
+                                           ctx->page->priv->source);
        } else if (strcmp (element_name, "FixedPage") == 0) {
                /* Do Nothing */
        } else {
@@ -956,7 +1079,15 @@ render_end_element (GMarkupParseContext  *context,
                }
                cairo_restore (ctx->cr);
                GXPS_DEBUG (g_message ("restore"));
+               if (canvas->pop_resource_dict) {
+                       GXPSResources *resources;
+
+                       resources = gxps_archive_get_resources (ctx->page->priv->zip);
+                       gxps_resources_pop_dict (resources);
+               }
                gxps_canvas_free (canvas);
+       } else if (strcmp (element_name, "FixedPage.Resources") == 0) {
+               gxps_resources_parser_pop (context);
        } else if (strcmp (element_name, "FixedPage") == 0) {
                /* Do Nothing */
        } else {
diff --git a/libgxps/gxps-resources.c b/libgxps/gxps-resources.c
new file mode 100644
index 0000000..efe0398
--- /dev/null
+++ b/libgxps/gxps-resources.c
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2015  Jason Crain <jason aquaticape us>
+ * Copyright (C) 2017  Ignacio Casal Quinteiro <icq gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "gxps-resources.h"
+#include "gxps-parse-utils.h"
+#include "gxps-error.h"
+
+#include <string.h>
+
+#define GXPS_RESOURCES_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST (cls, GXPS_TYPE_RESOURCES, 
GXPSResourcesClass))
+#define GXPS_IS_RESOURCES_CLASS(obj)  (G_TYPE_CHECK_CLASS_TYPE (obj, GXPS_TYPE_RESOURCES))
+#define GXPS_RESOURCES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GXPS_TYPE_RESOURCES, 
GXPSResourcesClass))
+
+struct _GXPSResources
+{
+       GObject parent_instance;
+
+       GXPSArchive *zip;
+
+       GQueue *queue;
+};
+
+struct _GXPSResourcesClass
+{
+       GObjectClass parent;
+};
+
+typedef struct _GXPSResourcesClass GXPSResourcesClass;
+
+enum {
+       PROP_0,
+       PROP_ARCHIVE,
+       LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+G_DEFINE_TYPE (GXPSResources, gxps_resources, G_TYPE_OBJECT)
+
+static void
+gxps_resources_finalize (GObject *object)
+{
+       GXPSResources *resources = GXPS_RESOURCES (object);
+
+       g_queue_free_full (resources->queue, (GDestroyNotify)g_hash_table_destroy);
+       g_object_unref (resources->zip);
+
+       G_OBJECT_CLASS (gxps_resources_parent_class)->finalize (object);
+}
+
+static void
+gxps_resources_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+       GXPSResources *resources = GXPS_RESOURCES (object);
+
+       switch (prop_id) {
+       case PROP_ARCHIVE:
+               resources->zip = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gxps_resources_class_init (GXPSResourcesClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = gxps_resources_finalize;
+       object_class->set_property = gxps_resources_set_property;
+
+       props[PROP_ARCHIVE] =
+               g_param_spec_object ("archive",
+                                    "Archive",
+                                    "The document archive",
+                                    GXPS_TYPE_ARCHIVE,
+                                    G_PARAM_WRITABLE |
+                                    G_PARAM_CONSTRUCT_ONLY);
+
+       g_object_class_install_properties (object_class, LAST_PROP, props);
+}
+
+static void
+gxps_resources_init (GXPSResources *resources)
+{
+       resources->queue = g_queue_new ();
+}
+
+void
+gxps_resources_push_dict (GXPSResources *resources)
+{
+       GHashTable *ht;
+
+       g_return_if_fail (GXPS_IS_RESOURCES (resources));
+
+       ht = g_hash_table_new_full (g_str_hash,
+                                   g_str_equal,
+                                   (GDestroyNotify)g_free,
+                                   (GDestroyNotify)g_free);
+       g_queue_push_head (resources->queue, ht);
+}
+
+void
+gxps_resources_pop_dict (GXPSResources *resources)
+{
+       GHashTable *ht;
+
+       g_return_if_fail (GXPS_IS_RESOURCES (resources));
+
+       ht = g_queue_pop_head (resources->queue);
+       g_hash_table_destroy (ht);
+}
+
+const gchar *
+gxps_resources_get_resource (GXPSResources *resources,
+                             const gchar   *key)
+{
+       GList *node;
+
+       g_return_val_if_fail (GXPS_IS_RESOURCES (resources), NULL);
+
+       for (node = resources->queue->head; node != NULL; node = node->next) {
+               GHashTable *ht;
+               gpointer data;
+
+               ht = node->data;
+               data = g_hash_table_lookup (ht, key);
+               if (data)
+                       return data;
+       }
+
+       return NULL;
+}
+
+static gboolean
+gxps_resources_set (GXPSResources *resources,
+                    gchar         *key,
+                    gchar         *value)
+{
+       GHashTable *ht;
+
+       if (g_queue_get_length (resources->queue) == 0)
+               gxps_resources_push_dict (resources);
+
+       ht = g_queue_peek_head (resources->queue);
+       if (g_hash_table_contains (ht, key)) {
+               g_free (key);
+               g_free (value);
+               return FALSE;
+       }
+
+       g_hash_table_insert (ht, key, value);
+
+       return TRUE;
+}
+
+typedef struct {
+       GXPSResources *resources;
+       gchar *source;
+
+       gchar *key;
+       GString *xml;
+} GXPSResourceDictContext;
+
+static GXPSResourceDictContext *
+gxps_resource_dict_context_new (GXPSResources *resources,
+                                const gchar   *source)
+{
+       GXPSResourceDictContext *resource_dict;
+
+       resource_dict = g_slice_new0 (GXPSResourceDictContext);
+       resource_dict->resources = g_object_ref (resources);
+       resource_dict->source = g_strdup (source);
+
+       return resource_dict;
+}
+
+static void
+gxps_resource_dict_context_free (GXPSResourceDictContext *resource_dict)
+{
+       if (G_UNLIKELY (!resource_dict))
+               return;
+
+       g_free (resource_dict->key);
+       if (resource_dict->xml)
+               g_string_free (resource_dict->xml, TRUE);
+       g_object_unref (resource_dict->resources);
+       g_slice_free (GXPSResourceDictContext, resource_dict);
+}
+
+static void
+resource_concat_start_element (GMarkupParseContext  *context,
+                              const gchar          *element_name,
+                              const gchar         **names,
+                              const gchar         **values,
+                              gpointer              user_data,
+                              GError              **error)
+{
+       GXPSResourceDictContext *resource_dict_ctx = (GXPSResourceDictContext *)user_data;
+       gint i;
+
+       g_string_append_printf (resource_dict_ctx->xml, "<%s",
+                               element_name);
+       for (i = 0; names[i] != NULL; i++) {
+               g_string_append_printf (resource_dict_ctx->xml,
+                                       " %s=\"%s\"",
+                                       names[i], values[i]);
+       }
+
+       g_string_append (resource_dict_ctx->xml, ">\n");
+}
+
+static void
+resource_concat_end_element (GMarkupParseContext  *context,
+                            const gchar          *element_name,
+                            gpointer              user_data,
+                            GError              **error)
+{
+       GXPSResourceDictContext *resource_dict_ctx = (GXPSResourceDictContext *)user_data;
+
+       g_string_append_printf (resource_dict_ctx->xml, "</%s>\n",
+                               element_name);
+}
+
+static GMarkupParser resource_concat_parser = {
+       resource_concat_start_element,
+       resource_concat_end_element,
+       NULL,
+       NULL,
+       NULL
+};
+
+static void
+resource_dict_start_element (GMarkupParseContext  *context,
+                            const gchar          *element_name,
+                            const gchar         **names,
+                            const gchar         **values,
+                            gpointer              user_data,
+                            GError              **error)
+{
+       GXPSResourceDictContext *resource_dict_ctx = (GXPSResourceDictContext *)user_data;
+       gint i;
+
+       for (i = 0; names[i] != NULL; i++) {
+               if (strcmp (names[i], "x:Key") == 0) {
+                       resource_dict_ctx->key = g_strdup (values[i]);
+                       break;
+               }
+       }
+
+       if (!resource_dict_ctx->key) {
+               gxps_parse_error (context,
+                                 resource_dict_ctx->source,
+                                 G_MARKUP_ERROR_MISSING_ATTRIBUTE,
+                                 element_name, "x:Key",
+                                 NULL, error);
+               return;
+       }
+
+       if (!resource_dict_ctx->xml) {
+               resource_dict_ctx->xml = g_string_new (NULL);
+               g_string_append_printf (resource_dict_ctx->xml, "<%s>\n",
+                                       element_name);
+       }
+
+       g_string_append_printf (resource_dict_ctx->xml, "<%s",
+                               element_name);
+       for (i = 0; names[i] != NULL; i++) {
+               /* Skip key */
+               if (strcmp (names[i], "x:Key") != 0) {
+                       g_string_append_printf (resource_dict_ctx->xml,
+                                               " %s=\"%s\"",
+                                               names[i], values[i]);
+               }
+       }
+
+       g_string_append (resource_dict_ctx->xml, ">\n");
+
+       g_markup_parse_context_push (context, &resource_concat_parser, resource_dict_ctx);
+}
+
+static void
+resource_dict_end_element (GMarkupParseContext  *context,
+                          const gchar          *element_name,
+                          gpointer              user_data,
+                          GError              **error)
+{
+       GXPSResourceDictContext *resource_dict_ctx = (GXPSResourceDictContext *)user_data;
+
+       g_string_append_printf (resource_dict_ctx->xml, "</%s>\n</%s>",
+                               element_name, element_name);
+       gxps_resources_set (resource_dict_ctx->resources,
+                           resource_dict_ctx->key,
+                           g_string_free (resource_dict_ctx->xml, FALSE));
+       resource_dict_ctx->key = NULL;
+       resource_dict_ctx->xml = NULL;
+       g_markup_parse_context_pop (context);
+}
+
+static void
+resource_dict_error (GMarkupParseContext *context,
+                    GError              *error,
+                    gpointer             user_data)
+{
+       GXPSResourceDictContext *resource_dict_ctx = (GXPSResourceDictContext *)user_data;
+       gxps_resource_dict_context_free (resource_dict_ctx);
+}
+
+static GMarkupParser resource_dict_parser = {
+       resource_dict_start_element,
+       resource_dict_end_element,
+       NULL,
+       NULL,
+       resource_dict_error
+};
+
+typedef struct
+{
+       GXPSResources *resources;
+       gchar *source;
+       gboolean remote;
+} GXPSResourceContext;
+
+static GXPSResourceContext *
+gxps_resource_context_new (GXPSResources *resources,
+                           const gchar   *source)
+{
+       GXPSResourceContext *context;
+
+       context = g_slice_new0 (GXPSResourceContext);
+       context->resources = g_object_ref (resources);
+       context->source = g_strdup (source);
+
+       return context;
+}
+
+static void
+gxps_resource_context_free (GXPSResourceContext *context)
+{
+       g_object_unref (context->resources);
+       g_free (context->source);
+       g_slice_free (GXPSResourceContext, context);
+}
+
+static void
+push_resource_dict_context (GMarkupParseContext *context,
+                            GXPSResourceContext *rcontext)
+{
+       GXPSResourceDictContext *resource_dict_ctx;
+
+       resource_dict_ctx = gxps_resource_dict_context_new (rcontext->resources,
+                                                           rcontext->source);
+       g_markup_parse_context_push (context, &resource_dict_parser, resource_dict_ctx);
+}
+
+static void
+pop_resource_dict_context (GMarkupParseContext *context)
+{
+       GXPSResourceDictContext *resource_dict_ctx;
+
+       resource_dict_ctx = g_markup_parse_context_pop (context);
+       gxps_resource_dict_context_free (resource_dict_ctx);
+}
+
+static void
+remote_resource_start_element (GMarkupParseContext  *context,
+                              const gchar          *element_name,
+                              const gchar         **names,
+                              const gchar         **values,
+                              gpointer              user_data,
+                              GError              **error)
+{
+       GXPSResourceContext *rcontext = (GXPSResourceContext *)user_data;
+
+       if (strcmp (element_name, "ResourceDictionary") == 0) {
+               push_resource_dict_context (context, rcontext);
+       } else {
+               gxps_parse_error (context,
+                                 rcontext->source,
+                                 G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                                 element_name, NULL, NULL, error);
+       }
+}
+
+static void
+remote_resource_end_element (GMarkupParseContext  *context,
+                            const gchar          *element_name,
+                            gpointer              user_data,
+                            GError              **error)
+{
+       if (strcmp (element_name, "ResourceDictionary") == 0) {
+               pop_resource_dict_context (context);
+       }
+}
+
+static GMarkupParser remote_resource_parser = {
+       remote_resource_start_element,
+       remote_resource_end_element,
+       NULL,
+       NULL,
+       NULL
+};
+
+static void
+resources_start_element (GMarkupParseContext  *context,
+                        const gchar          *element_name,
+                        const gchar         **names,
+                        const gchar         **values,
+                        gpointer              user_data,
+                        GError              **error)
+{
+       GXPSResourceContext *rcontext = (GXPSResourceContext *)user_data;
+       const gchar *source = NULL;
+       gint i;
+
+       if (strcmp (element_name, "ResourceDictionary") == 0) {
+               for (i = 0; names[i] != NULL; i++) {
+                       if (strcmp (names[i], "Source") == 0)
+                               source = values[i];
+               }
+
+               rcontext->remote = source != NULL;
+               if (rcontext->remote) {
+                       GInputStream *stream;
+                       gchar *abs_source;
+                       GMarkupParseContext *parse_ctx;
+
+                       abs_source = gxps_resolve_relative_path (rcontext->source,
+                                                                source);
+                       stream = gxps_archive_open (rcontext->resources->zip, abs_source);
+                       if (!stream) {
+                               g_set_error (error,
+                                            GXPS_ERROR,
+                                            GXPS_ERROR_SOURCE_NOT_FOUND,
+                                            "Source %s not found in archive",
+                                            abs_source);
+                               g_free (abs_source);
+                               return;
+                       }
+
+                       parse_ctx = g_markup_parse_context_new (&remote_resource_parser,
+                                                               0, rcontext, NULL);
+                       gxps_parse_stream (parse_ctx, stream, error);
+                       g_object_unref (stream);
+                       g_markup_parse_context_free (parse_ctx);
+                       g_free (abs_source);
+               } else {
+                       push_resource_dict_context (context, rcontext);
+               }
+       } else {
+               gxps_parse_error (context,
+                                 rcontext->source,
+                                 G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                                 element_name, NULL, NULL, error);
+       }
+}
+
+static void
+resources_end_element (GMarkupParseContext  *context,
+                      const gchar          *element_name,
+                      gpointer              user_data,
+                      GError              **error)
+{
+       GXPSResourceContext *rcontext = (GXPSResourceContext *)user_data;
+
+       if (strcmp (element_name, "ResourceDictionary") == 0) {
+               if (!rcontext->remote) {
+                       pop_resource_dict_context (context);
+               } else {
+                       rcontext->remote = FALSE;
+               }
+       }
+}
+
+static GMarkupParser resources_parser = {
+       resources_start_element,
+       resources_end_element,
+       NULL,
+       NULL,
+       NULL
+};
+
+void
+gxps_resources_parser_push (GMarkupParseContext  *context,
+                           GXPSResources        *resources,
+                           const gchar          *source)
+{
+       GXPSResourceContext *rcontext;
+
+       rcontext = gxps_resource_context_new (resources, source);
+
+       g_markup_parse_context_push (context, &resources_parser, rcontext);
+}
+
+void
+gxps_resources_parser_pop (GMarkupParseContext  *context)
+{
+       GXPSResourceContext *rcontext;
+
+       rcontext = g_markup_parse_context_pop (context);
+       gxps_resource_context_free (rcontext);
+}
diff --git a/libgxps/gxps-resources.h b/libgxps/gxps-resources.h
new file mode 100644
index 0000000..65e6d6d
--- /dev/null
+++ b/libgxps/gxps-resources.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015  Jason Crain <jason aquaticape us>
+ * Copyright (C) 2017  Ignacio Casal Quinteiro <icq gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef GXPS_RESOURCES_H
+#define GXPS_RESOURCES_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GXPS_TYPE_RESOURCES           (gxps_resources_get_type ())
+#define GXPS_RESOURCES(obj)           (G_TYPE_CHECK_INSTANCE_CAST (obj, GXPS_TYPE_RESOURCES, GXPSResources))
+#define GXPS_IS_RESOURCES(obj)        (G_TYPE_CHECK_INSTANCE_TYPE (obj, GXPS_TYPE_RESOURCES))
+
+typedef struct _GXPSResources GXPSResources;
+
+GType          gxps_resources_get_type     (void) G_GNUC_CONST;
+
+void           gxps_resources_push_dict    (GXPSResources       *resources);
+
+void           gxps_resources_pop_dict     (GXPSResources       *resources);
+
+const gchar   *gxps_resources_get_resource (GXPSResources       *resources,
+                                            const gchar         *key);
+
+void           gxps_resources_parser_push  (GMarkupParseContext *context,
+                                            GXPSResources       *resources,
+                                            const gchar         *source);
+
+void           gxps_resources_parser_pop   (GMarkupParseContext *context);
+
+G_END_DECLS
+
+#endif /* GXPS_RESOURCES_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]