diff options
Diffstat (limited to 'src/yelp-transform.c')
-rw-r--r-- | src/yelp-transform.c | 416 |
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) +{ +} |