data/domains: Drop unused msgs.xsl
[yelp.git] / libyelp / yelp-transform.c
blobeecfe6a7acc9fc4123f1a6864be0e287f9586146
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2003-2009 Shaun McCance <shaunm@gnome.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Author: Shaun McCance <shaunm@gnome.org>
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <glib-object.h>
30 #include <libxml/parser.h>
31 #include <libxml/parserInternals.h>
32 #include <libxml/xinclude.h>
33 #include <libxml/xpathInternals.h>
34 #include <libxslt/documents.h>
35 #include <libxslt/xslt.h>
36 #include <libexslt/exslt.h>
37 #include <libxslt/templates.h>
38 #include <libxslt/transform.h>
39 #include <libxslt/extensions.h>
40 #include <libxslt/xsltInternals.h>
41 #include <libxslt/xsltutils.h>
43 #include "yelp-debug.h"
44 #include "yelp-error.h"
45 #include "yelp-transform.h"
47 #define YELP_NAMESPACE "http://www.gnome.org/yelp/ns"
49 static void yelp_transform_init (YelpTransform *transform);
50 static void yelp_transform_class_init (YelpTransformClass *klass);
51 static void yelp_transform_dispose (GObject *object);
52 static void yelp_transform_finalize (GObject *object);
53 static void yelp_transform_get_property (GObject *object,
54 guint prop_id,
55 GValue *value,
56 GParamSpec *pspec);
57 static void yelp_transform_set_property (GObject *object,
58 guint prop_id,
59 const GValue *value,
60 GParamSpec *pspec);
62 static void transform_run (YelpTransform *transform);
63 static gboolean transform_free (YelpTransform *transform);
64 static void transform_set_error (YelpTransform *transform,
65 YelpError *error);
67 static gboolean transform_chunk (YelpTransform *transform);
68 static gboolean transform_error (YelpTransform *transform);
69 static gboolean transform_final (YelpTransform *transform);
71 static void xslt_yelp_document (xsltTransformContextPtr ctxt,
72 xmlNodePtr node,
73 xmlNodePtr inst,
74 xsltStylePreCompPtr comp);
75 static void xslt_yelp_cache (xsltTransformContextPtr ctxt,
76 xmlNodePtr node,
77 xmlNodePtr inst,
78 xsltStylePreCompPtr comp);
79 static void xslt_yelp_aux (xmlXPathParserContextPtr ctxt,
80 int nargs);
82 enum {
83 PROP_0,
84 PROP_STYLESHEET
87 enum {
88 CHUNK_READY,
89 FINISHED,
90 ERROR,
91 LAST_SIGNAL
93 static gint signals[LAST_SIGNAL] = { 0 };
95 G_DEFINE_TYPE (YelpTransform, yelp_transform, G_TYPE_OBJECT);
96 #define GET_PRIV(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_TRANSFORM, YelpTransformPrivate))
98 typedef struct _YelpTransformPrivate YelpTransformPrivate;
99 struct _YelpTransformPrivate {
100 xmlDocPtr input;
101 xmlDocPtr output;
102 gchar *stylesheet_file;
103 xsltStylesheetPtr stylesheet;
104 xsltTransformContextPtr context;
106 xmlDocPtr aux;
107 xsltDocumentPtr aux_xslt;
109 gchar **params;
111 GThread *thread;
112 GMutex *mutex;
113 GAsyncQueue *queue;
114 GHashTable *chunks;
116 gboolean running;
117 gboolean cancelled;
119 GError *error;
122 /******************************************************************************/
124 static void
125 yelp_transform_init (YelpTransform *transform)
127 YelpTransformPrivate *priv = GET_PRIV (transform);
128 priv->queue = g_async_queue_new ();
129 priv->chunks = g_hash_table_new_full (g_str_hash,
130 g_str_equal,
131 g_free,
132 NULL);
135 static void
136 yelp_transform_class_init (YelpTransformClass *klass)
138 exsltRegisterAll ();
140 GObjectClass *object_class = G_OBJECT_CLASS (klass);
142 object_class->dispose = yelp_transform_dispose;
143 object_class->finalize = yelp_transform_finalize;
144 object_class->get_property = yelp_transform_get_property;
145 object_class->set_property = yelp_transform_set_property;
147 signals[CHUNK_READY] = g_signal_new ("chunk-ready",
148 G_TYPE_FROM_CLASS (klass),
149 G_SIGNAL_RUN_LAST,
150 0, NULL, NULL,
151 g_cclosure_marshal_VOID__STRING,
152 G_TYPE_NONE, 1, G_TYPE_STRING);
153 signals[FINISHED] = g_signal_new ("finished",
154 G_TYPE_FROM_CLASS (klass),
155 G_SIGNAL_RUN_LAST,
156 0, NULL, NULL,
157 g_cclosure_marshal_VOID__VOID,
158 G_TYPE_NONE, 0);
159 signals[ERROR] = g_signal_new ("error",
160 G_TYPE_FROM_CLASS (klass),
161 G_SIGNAL_RUN_LAST,
162 0, NULL, NULL,
163 g_cclosure_marshal_VOID__VOID,
164 G_TYPE_NONE, 0);
166 g_type_class_add_private (klass, sizeof (YelpTransformPrivate));
168 g_object_class_install_property (object_class,
169 PROP_STYLESHEET,
170 g_param_spec_string ("stylesheet",
171 N_("XSLT Stylesheet"),
172 N_("The location of the XSLT stylesheet"),
173 NULL,
174 G_PARAM_CONSTRUCT_ONLY |
175 G_PARAM_READWRITE | G_PARAM_STATIC_NAME |
176 G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
179 static void
180 yelp_transform_dispose (GObject *object)
182 YelpTransformPrivate *priv = GET_PRIV (object);
184 debug_print (DB_FUNCTION, "entering\n");
186 if (priv->queue) {
187 gchar *chunk_id;
188 while ((chunk_id = (gchar *) g_async_queue_try_pop (priv->queue)))
189 g_free (chunk_id);
190 g_async_queue_unref (priv->queue);
191 priv->queue = NULL;
194 /* We do not free input or aux. They belong to the caller, which
195 must ensure they exist for the lifetime of the transform. We
196 have to set priv->aux_xslt->doc (which is priv->aux) to NULL
197 before xsltFreeTransformContext. Otherwise it will be freed,
198 which we don't want.
200 if (priv->aux_xslt)
201 priv->aux_xslt->doc = NULL;
203 /* We free these in dispose to make absolutely certain that they're
204 freed by the time any weak notify callbacks are called. These
205 may be used elsewhere to free resources like the input document.
207 if (priv->context) {
208 xsltFreeTransformContext (priv->context);
209 priv->context = NULL;
211 if (priv->stylesheet) {
212 xsltFreeStylesheet (priv->stylesheet);
213 priv->stylesheet = NULL;
215 if (priv->output) {
216 xmlFreeDoc (priv->output);
217 priv->output = NULL;
220 G_OBJECT_CLASS (yelp_transform_parent_class)->dispose (object);
223 static void
224 yelp_transform_finalize (GObject *object)
226 YelpTransformPrivate *priv = GET_PRIV (object);
227 xsltDocumentPtr xsltdoc;
228 GHashTableIter iter;
229 gpointer chunk;
231 debug_print (DB_FUNCTION, "entering\n");
233 if (priv->error)
234 g_error_free (priv->error);
236 g_hash_table_iter_init (&iter, priv->chunks);
237 while (g_hash_table_iter_next (&iter, NULL, &chunk))
238 g_free (chunk);
239 g_hash_table_destroy (priv->chunks);
241 g_strfreev (priv->params);
242 g_mutex_free (priv->mutex);
244 G_OBJECT_CLASS (yelp_transform_parent_class)->finalize (object);
247 static void
248 yelp_transform_get_property (GObject *object,
249 guint prop_id,
250 GValue *value,
251 GParamSpec *pspec)
253 YelpTransformPrivate *priv = GET_PRIV (object);
255 switch (prop_id)
257 case PROP_STYLESHEET:
258 g_value_set_string (value, priv->stylesheet_file);
259 break;
260 default:
261 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
262 break;
266 static void
267 yelp_transform_set_property (GObject *object,
268 guint prop_id,
269 const GValue *value,
270 GParamSpec *pspec)
272 YelpTransformPrivate *priv = GET_PRIV (object);
274 switch (prop_id)
276 case PROP_STYLESHEET:
277 priv->stylesheet_file = g_value_dup_string (value);
278 break;
279 default:
280 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
281 break;
285 /******************************************************************************/
287 YelpTransform *
288 yelp_transform_new (const gchar *stylesheet)
290 return (YelpTransform *) g_object_new (YELP_TYPE_TRANSFORM,
291 "stylesheet", stylesheet,
292 NULL);
295 gboolean
296 yelp_transform_start (YelpTransform *transform,
297 xmlDocPtr document,
298 xmlDocPtr auxiliary,
299 const gchar * const *params)
301 YelpTransformPrivate *priv = GET_PRIV (transform);
303 priv->input = document;
304 priv->aux = auxiliary;
305 priv->params = g_strdupv ((gchar **) params);
307 priv->mutex = g_mutex_new ();
308 g_mutex_lock (priv->mutex);
309 priv->running = TRUE;
310 g_object_ref (transform);
311 priv->thread = g_thread_create ((GThreadFunc) transform_run,
312 transform, FALSE, NULL);
313 g_mutex_unlock (priv->mutex);
315 return TRUE;
318 gchar *
319 yelp_transform_take_chunk (YelpTransform *transform,
320 const gchar *chunk_id)
322 YelpTransformPrivate *priv = GET_PRIV (transform);
323 gchar *buf;
325 g_mutex_lock (priv->mutex);
327 buf = g_hash_table_lookup (priv->chunks, chunk_id);
328 if (buf)
329 g_hash_table_remove (priv->chunks, chunk_id);
331 g_mutex_unlock (priv->mutex);
333 /* The caller assumes ownership of this memory. */
334 return buf;
337 void
338 yelp_transform_cancel (YelpTransform *transform)
340 YelpTransformPrivate *priv = GET_PRIV (transform);
341 g_mutex_lock (priv->mutex);
342 if (priv->running) {
343 priv->cancelled = TRUE;
344 if (priv->context)
345 priv->context->state = XSLT_STATE_STOPPED;
347 g_mutex_unlock (priv->mutex);
350 GError *
351 yelp_transform_get_error (YelpTransform *transform)
353 YelpTransformPrivate *priv = GET_PRIV (transform);
354 GError *ret = NULL;
356 g_mutex_lock (priv->mutex);
357 if (priv->error)
358 ret = g_error_copy (priv->error);
359 g_mutex_unlock (priv->mutex);
361 return ret;
364 /******************************************************************************/
366 static void
367 transform_run (YelpTransform *transform)
369 YelpTransformPrivate *priv = GET_PRIV (transform);
371 debug_print (DB_FUNCTION, "entering\n");
373 priv->stylesheet = xsltParseStylesheetFile (BAD_CAST (priv->stylesheet_file));
374 if (priv->stylesheet == NULL) {
375 g_mutex_lock (priv->mutex);
376 if (priv->error)
377 g_error_free (priv->error);
378 priv->error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING,
379 _("The XSLT stylesheet ā€˜%sā€™ is either missing or not valid."),
380 priv->stylesheet_file);
381 g_object_ref (transform);
382 g_idle_add ((GSourceFunc) transform_error, transform);
383 g_mutex_unlock (priv->mutex);
384 return;
387 priv->context = xsltNewTransformContext (priv->stylesheet,
388 priv->input);
389 if (priv->context == NULL) {
390 g_mutex_lock (priv->mutex);
391 if (priv->error)
392 g_error_free (priv->error);
393 priv->error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING,
394 _("The XSLT stylesheet ā€˜%sā€™ is either missing or not valid."),
395 priv->stylesheet_file);
396 g_object_ref (transform);
397 g_idle_add ((GSourceFunc) transform_error, transform);
398 g_mutex_unlock (priv->mutex);
399 return;
402 priv->context->_private = transform;
403 xsltRegisterExtElement (priv->context,
404 BAD_CAST "document",
405 BAD_CAST YELP_NAMESPACE,
406 (xsltTransformFunction) xslt_yelp_document);
407 xsltRegisterExtElement (priv->context,
408 BAD_CAST "cache",
409 BAD_CAST YELP_NAMESPACE,
410 (xsltTransformFunction) xslt_yelp_cache);
411 xsltRegisterExtFunction (priv->context,
412 BAD_CAST "input",
413 BAD_CAST YELP_NAMESPACE,
414 (xmlXPathFunction) xslt_yelp_aux);
416 priv->output = xsltApplyStylesheetUser (priv->stylesheet,
417 priv->input,
418 (const char **) priv->params,
419 NULL, NULL,
420 priv->context);
421 g_mutex_lock (priv->mutex);
422 priv->running = FALSE;
423 if (!priv->cancelled) {
424 g_idle_add ((GSourceFunc) transform_final, transform);
425 g_mutex_unlock (priv->mutex);
427 else {
428 g_mutex_unlock (priv->mutex);
429 g_object_unref (transform);
433 static gboolean
434 transform_chunk (YelpTransform *transform)
436 YelpTransformPrivate *priv = GET_PRIV (transform);
437 gchar *chunk_id;
439 debug_print (DB_FUNCTION, "entering\n");
441 if (priv->cancelled)
442 goto done;
444 chunk_id = (gchar *) g_async_queue_try_pop (priv->queue);
446 g_signal_emit (transform, signals[CHUNK_READY], 0, chunk_id);
448 g_free (chunk_id);
450 done:
451 g_object_unref (transform);
452 return FALSE;
455 static gboolean
456 transform_error (YelpTransform *transform)
458 YelpTransformPrivate *priv = GET_PRIV (transform);
460 debug_print (DB_FUNCTION, "entering\n");
462 if (priv->cancelled)
463 goto done;
465 g_signal_emit (transform, signals[ERROR], 0);
467 done:
468 g_object_unref (transform);
469 return FALSE;
472 static gboolean
473 transform_final (YelpTransform *transform)
475 YelpTransformPrivate *priv = GET_PRIV (transform);
477 debug_print (DB_FUNCTION, "entering\n");
479 if (priv->cancelled)
480 goto done;
482 g_signal_emit (transform, signals[FINISHED], 0);
484 done:
485 g_object_unref (transform);
486 return FALSE;
489 /******************************************************************************/
491 static void
492 xslt_yelp_document (xsltTransformContextPtr ctxt,
493 xmlNodePtr node,
494 xmlNodePtr inst,
495 xsltStylePreCompPtr comp)
497 YelpTransform *transform;
498 YelpTransformPrivate *priv;
499 xmlChar *page_id = NULL;
500 gchar *temp;
501 xmlChar *page_buf;
502 gint buf_size;
503 xsltStylesheetPtr style = NULL;
504 const char *old_outfile;
505 xmlDocPtr new_doc = NULL;
506 xmlDocPtr old_doc;
507 xmlNodePtr old_insert;
509 debug_print (DB_FUNCTION, "entering\n");
511 if (ctxt->state == XSLT_STATE_STOPPED)
512 return;
514 if (!ctxt || !node || !inst || !comp)
515 return;
517 transform = YELP_TRANSFORM (ctxt->_private);
518 priv = GET_PRIV (transform);
520 page_id = xsltEvalAttrValueTemplate (ctxt, inst,
521 (const xmlChar *) "href",
522 NULL);
523 if (page_id == NULL || *page_id == '\0') {
524 if (page_id)
525 xmlFree (page_id);
526 else
527 xsltTransformError (ctxt, NULL, inst,
528 _("No href attribute found on "
529 "yelp:document\n"));
530 /* FIXME: put a real error here */
531 goto done;
533 debug_print (DB_ARG, " page_id = \"%s\"\n", page_id);
535 old_outfile = ctxt->outputFile;
536 old_doc = ctxt->output;
537 old_insert = ctxt->insert;
538 ctxt->outputFile = (const char *) page_id;
540 style = xsltNewStylesheet ();
541 if (style == NULL) {
542 xsltTransformError (ctxt, NULL, inst,
543 _("Out of memory"));
544 goto done;
547 style->omitXmlDeclaration = TRUE;
549 new_doc = xmlNewDoc (BAD_CAST "1.0");
550 new_doc->charset = XML_CHAR_ENCODING_UTF8;
551 new_doc->dict = ctxt->dict;
552 xmlDictReference (new_doc->dict);
554 ctxt->output = new_doc;
555 ctxt->insert = (xmlNodePtr) new_doc;
557 xsltApplyOneTemplate (ctxt, node, inst->children, NULL, NULL);
558 xsltSaveResultToString (&page_buf, &buf_size, new_doc, style);
560 ctxt->outputFile = old_outfile;
561 ctxt->output = old_doc;
562 ctxt->insert = old_insert;
564 g_mutex_lock (priv->mutex);
566 temp = g_strdup ((gchar *) page_id);
567 xmlFree (page_id);
569 g_async_queue_push (priv->queue, g_strdup ((gchar *) temp));
570 g_hash_table_insert (priv->chunks, temp, page_buf);
572 g_object_ref (transform);
573 g_idle_add ((GSourceFunc) transform_chunk, transform);
575 g_mutex_unlock (priv->mutex);
577 done:
578 if (new_doc)
579 xmlFreeDoc (new_doc);
580 if (style)
581 xsltFreeStylesheet (style);
584 static void
585 xslt_yelp_cache (xsltTransformContextPtr ctxt,
586 xmlNodePtr node,
587 xmlNodePtr inst,
588 xsltStylePreCompPtr comp)
592 static void
593 xslt_yelp_aux (xmlXPathParserContextPtr ctxt, int nargs)
595 xsltTransformContextPtr tctxt;
596 xmlXPathObjectPtr ret;
597 YelpTransform *transform;
598 YelpTransformPrivate *priv;
600 tctxt = xsltXPathGetTransformContext (ctxt);
601 transform = YELP_TRANSFORM (tctxt->_private);
602 priv = GET_PRIV (transform);
604 priv->aux_xslt = xsltNewDocument (tctxt, priv->aux);
606 ret = xmlXPathNewNodeSet (xmlDocGetRootElement (priv->aux));
607 xsltExtensionInstructionResultRegister (tctxt, ret);
608 valuePush (ctxt, ret);