summaryrefslogtreecommitdiff
path: root/src/yelp-transform.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/yelp-transform.c')
-rw-r--r--src/yelp-transform.c416
1 files changed, 416 insertions, 0 deletions
diff --git a/src/yelp-transform.c b/src/yelp-transform.c
new file mode 100644
index 00000000..1e6f8f5b
--- /dev/null
+++ b/src/yelp-transform.c
@@ -0,0 +1,416 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * Copyright (C) 2003-2007 Shaun McCance <shaunm@gnome.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Shaun McCance <shaunm@gnome.org>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <libxml/parser.h>
+#include <libxml/parserInternals.h>
+#include <libxml/xinclude.h>
+#include <libxslt/xslt.h>
+#include <libxslt/templates.h>
+#include <libxslt/transform.h>
+#include <libxslt/extensions.h>
+#include <libxslt/xsltInternals.h>
+#include <libxslt/xsltutils.h>
+
+#include "yelp-debug.h"
+#include "yelp-error.h"
+#include "yelp-transform.h"
+
+#define YELP_NAMESPACE "http://www.gnome.org/yelp/ns"
+
+static void transform_run (YelpTransform *transform);
+static gboolean transform_free (YelpTransform *transform);
+static void transform_set_error (YelpTransform *transform,
+ YelpError *error);
+
+static gboolean transform_chunk (YelpTransform *transform);
+static gboolean transform_error (YelpTransform *transform);
+static gboolean transform_final (YelpTransform *transform);
+
+static void xslt_yelp_document (xsltTransformContextPtr ctxt,
+ xmlNodePtr node,
+ xmlNodePtr inst,
+ xsltStylePreCompPtr comp);
+static void xslt_yelp_cache (xsltTransformContextPtr ctxt,
+ xmlNodePtr node,
+ xmlNodePtr inst,
+ xsltStylePreCompPtr comp);
+
+/******************************************************************************/
+
+YelpTransform
+*yelp_transform_new (gchar *stylesheet,
+ YelpTransformFunc func,
+ gpointer user_data)
+{
+ YelpTransform *transform;
+
+ transform = g_new0 (YelpTransform, 1);
+
+ transform->stylesheet = xsltParseStylesheetFile (BAD_CAST stylesheet);
+ if (!transform->stylesheet) {
+ transform->error =
+ yelp_error_new (_("Invalid Stylesheet"),
+ _("The XSLT stylesheet ā€˜%sā€™ is either missing, or it is "
+ "not valid."),
+ stylesheet);
+ transform_error (transform);
+ return NULL;
+ }
+
+ transform->func = func;
+
+ transform->queue = g_async_queue_new ();
+ transform->chunks = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ transform->user_data = user_data;
+
+ return transform;
+}
+
+void
+yelp_transform_start (YelpTransform *transform,
+ xmlDocPtr document,
+ gchar **params)
+{
+ transform->inputDoc = document;
+
+ transform->context = xsltNewTransformContext (transform->stylesheet,
+ transform->inputDoc);
+ if (!transform->context) {
+ YelpError *error =
+ yelp_error_new (_("Broken Transformation"),
+ _("An unknown error occurred while attempting to "
+ "transform the document."));
+ transform_set_error (transform, error);
+ return;
+ }
+
+ transform->params = g_strdupv (params);
+
+ transform->context->_private = transform;
+ xsltRegisterExtElement (transform->context,
+ BAD_CAST "document",
+ BAD_CAST YELP_NAMESPACE,
+ (xsltTransformFunction) xslt_yelp_document);
+ xsltRegisterExtElement (transform->context,
+ BAD_CAST "cache",
+ BAD_CAST YELP_NAMESPACE,
+ (xsltTransformFunction) xslt_yelp_cache);
+
+ transform->mutex = g_mutex_new ();
+ g_mutex_lock (transform->mutex);
+ transform->running = TRUE;
+ transform->thread = g_thread_create ((GThreadFunc) transform_run,
+ transform, FALSE, NULL);
+ g_mutex_unlock (transform->mutex);
+}
+
+gchar *
+yelp_transform_eat_chunk (YelpTransform *transform, gchar *chunk_id)
+{
+ gchar *buf;
+
+ g_mutex_lock (transform->mutex);
+
+ buf = g_hash_table_lookup (transform->chunks, chunk_id);
+ if (buf)
+ g_hash_table_remove (transform->chunks, chunk_id);
+
+ g_mutex_unlock (transform->mutex);
+
+ /* The caller assumes ownership of this memory. */
+ return buf;
+}
+
+void
+yelp_transform_release (YelpTransform *transform)
+{
+ g_mutex_lock (transform->mutex);
+ if (transform->running) {
+ /* We can't free it just now, because the thread is running.
+ * Instead, we'll tell libxslt to stop and mark the transform
+ * as released.
+ */
+ transform->released = TRUE;
+ transform->context->state = XSLT_STATE_STOPPED;
+ } else {
+ /* We might still have pending pops from the queue, so just
+ * schedule transform_free.
+ */
+ g_idle_add ((GSourceFunc) transform_free, transform);
+ }
+ g_mutex_unlock (transform->mutex);
+}
+
+/******************************************************************************/
+
+static void
+transform_run (YelpTransform *transform)
+{
+ transform->outputDoc = xsltApplyStylesheetUser (transform->stylesheet,
+ transform->inputDoc,
+ (const char **) transform->params,
+ NULL, NULL,
+ transform->context);
+
+ /* FIXME: do something with outputDoc? */
+ transform->idle_funcs++;
+ g_idle_add ((GSourceFunc) transform_final, transform);
+
+ g_mutex_lock (transform->mutex);
+ transform->running = FALSE;
+ if (transform->released) {
+ /* The transform was released by its owner, but it couldn't
+ * be freed because this thread was running. But we're in
+ * a thread, and the main thread might still be popping stuff
+ * off the asynchronous queue. Schedule this for freeing.
+ */
+ g_idle_add ((GSourceFunc) transform_free, transform);
+ }
+ g_mutex_unlock (transform->mutex);
+}
+
+static gboolean
+transform_free (YelpTransform *transform)
+{
+ gchar *chunk_id;
+
+ /* If the queue isn't empty yet, try again later. But because
+ * threads scare me and I don't want runaway code, stop trying
+ * after an insane number of attempts and just leak.
+ */
+ if (transform->idle_funcs > 0) {
+ transform->free_attempts++;
+ if (transform->free_attempts < 1000) {
+ return TRUE;
+ } else {
+ g_warning ("Runaway free attempt detected. Memory is about to leak.\n");
+ return FALSE;
+ }
+ }
+
+ g_mutex_lock (transform->mutex);
+ if (transform->outputDoc)
+ xmlFreeDoc (transform->outputDoc);
+ if (transform->stylesheet)
+ xsltFreeStylesheet (transform->stylesheet);
+ if (transform->context)
+ xsltFreeTransformContext (transform->context);
+
+ g_strfreev (transform->params);
+
+ /* FIXME: destroy data */
+ while ((chunk_id = (gchar *) g_async_queue_try_pop (transform->queue)))
+ g_free (chunk_id);
+ g_async_queue_unref (transform->queue);
+ g_mutex_unlock (transform->mutex);
+ g_mutex_free (transform->mutex);
+
+ if (transform->error)
+ yelp_error_free (transform->error);
+
+ g_free (transform);
+ return FALSE;
+}
+
+static void
+transform_set_error (YelpTransform *transform,
+ YelpError *error)
+{
+ g_mutex_lock (transform->mutex);
+ if (transform->released) {
+ yelp_error_free (error);
+ g_mutex_unlock (transform->mutex);
+ return;
+ }
+ if (transform->error)
+ yelp_error_free (transform->error);
+ transform->error = error;
+ transform->idle_funcs++;
+ g_idle_add ((GSourceFunc) transform_error, transform);
+ g_mutex_unlock (transform->mutex);
+}
+
+static gboolean
+transform_chunk (YelpTransform *transform)
+{
+ gchar *chunk_id;
+
+ transform->idle_funcs--;
+ if (transform->released)
+ return FALSE;
+
+ chunk_id = (gchar *) g_async_queue_try_pop (transform->queue);
+
+ if (chunk_id) {
+ if (transform->func)
+ transform->func (transform,
+ YELP_TRANSFORM_CHUNK,
+ chunk_id,
+ transform->user_data);
+ else
+ g_free (chunk_id);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+transform_error (YelpTransform *transform)
+{
+ YelpError *error;
+
+ transform->idle_funcs--;
+ if (transform->released)
+ return FALSE;
+
+ g_mutex_lock (transform->mutex);
+
+ error = transform->error;
+ transform->error = NULL;
+
+ g_mutex_unlock (transform->mutex);
+
+ if (transform->func)
+ transform->func (transform,
+ YELP_TRANSFORM_ERROR,
+ error, transform->user_data);
+ else
+ yelp_error_free (error);
+
+ return FALSE;
+}
+
+static gboolean
+transform_final (YelpTransform *transform)
+{
+ transform->idle_funcs--;
+ if (transform->released)
+ return FALSE;
+
+ /* FIXME: check for anything remaining on the queue */
+ if (transform->func)
+ transform->func (transform,
+ YELP_TRANSFORM_FINAL,
+ NULL, transform->user_data);
+
+ return FALSE;
+}
+
+/******************************************************************************/
+
+static void
+xslt_yelp_document (xsltTransformContextPtr ctxt,
+ xmlNodePtr node,
+ xmlNodePtr inst,
+ xsltStylePreCompPtr comp)
+{
+ YelpTransform *transform;
+ xmlChar *page_id = NULL;
+ xmlChar *page_buf;
+ gint buf_size;
+ xsltStylesheetPtr style = NULL;
+ const char *old_outfile;
+ xmlDocPtr new_doc = NULL;
+ xmlDocPtr old_doc;
+ xmlNodePtr old_insert;
+
+ debug_print (DB_FUNCTION, "entering\n");
+
+ if (ctxt->state == XSLT_STATE_STOPPED)
+ return;
+
+ if (!ctxt || !node || !inst || !comp)
+ return;
+
+ transform = (YelpTransform *) ctxt->_private;
+
+ page_id = xsltEvalAttrValueTemplate (ctxt, inst,
+ (const xmlChar *) "href",
+ NULL);
+ if (page_id == NULL) {
+ xsltTransformError (ctxt, NULL, inst,
+ _("No href attribute found on yelp:document"));
+ /* FIXME: put a real error here */
+ goto done;
+ }
+ debug_print (DB_ARG, " page_id = \"%s\"\n", page_id);
+
+ old_outfile = ctxt->outputFile;
+ old_doc = ctxt->output;
+ old_insert = ctxt->insert;
+ ctxt->outputFile = (const char *) page_id;
+
+ style = xsltNewStylesheet ();
+ if (style == NULL) {
+ xsltTransformError (ctxt, NULL, inst,
+ _("Out of memory"));
+ goto done;
+ }
+
+ style->omitXmlDeclaration = TRUE;
+
+ new_doc = xmlNewDoc (BAD_CAST "1.0");
+ new_doc->charset = XML_CHAR_ENCODING_UTF8;
+ new_doc->dict = ctxt->dict;
+ xmlDictReference (new_doc->dict);
+
+ ctxt->output = new_doc;
+ ctxt->insert = (xmlNodePtr) new_doc;
+
+ xsltApplyOneTemplate (ctxt, node, inst->children, NULL, NULL);
+ xsltSaveResultToString (&page_buf, &buf_size, new_doc, style);
+
+ ctxt->outputFile = old_outfile;
+ ctxt->output = old_doc;
+ ctxt->insert = old_insert;
+
+ g_mutex_lock (transform->mutex);
+ g_hash_table_insert (transform->chunks, page_id, page_buf);
+ g_async_queue_push (transform->queue, g_strdup ((gchar *) page_id));
+ transform->idle_funcs++;
+ g_idle_add ((GSourceFunc) transform_chunk, transform);
+ g_mutex_unlock (transform->mutex);
+
+ done:
+ if (new_doc)
+ xmlFreeDoc (new_doc);
+ if (style)
+ xsltFreeStylesheet (style);
+}
+
+static void
+xslt_yelp_cache (xsltTransformContextPtr ctxt,
+ xmlNodePtr node,
+ xmlNodePtr inst,
+ xsltStylePreCompPtr comp)
+{
+}