1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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
,
57 static void yelp_transform_set_property (GObject
*object
,
62 static void transform_run (YelpTransform
*transform
);
63 static gboolean
transform_free (YelpTransform
*transform
);
64 static void transform_set_error (YelpTransform
*transform
,
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
,
74 xsltStylePreCompPtr comp
);
75 static void xslt_yelp_cache (xsltTransformContextPtr ctxt
,
78 xsltStylePreCompPtr comp
);
79 static void xslt_yelp_aux (xmlXPathParserContextPtr ctxt
,
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
{
102 gchar
*stylesheet_file
;
103 xsltStylesheetPtr stylesheet
;
104 xsltTransformContextPtr context
;
107 xsltDocumentPtr aux_xslt
;
122 /******************************************************************************/
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
,
136 yelp_transform_class_init (YelpTransformClass
*klass
)
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
),
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
),
157 g_cclosure_marshal_VOID__VOID
,
159 signals
[ERROR
] = g_signal_new ("error",
160 G_TYPE_FROM_CLASS (klass
),
163 g_cclosure_marshal_VOID__VOID
,
166 g_type_class_add_private (klass
, sizeof (YelpTransformPrivate
));
168 g_object_class_install_property (object_class
,
170 g_param_spec_string ("stylesheet",
171 N_("XSLT Stylesheet"),
172 N_("The location of the XSLT stylesheet"),
174 G_PARAM_CONSTRUCT_ONLY
|
175 G_PARAM_READWRITE
| G_PARAM_STATIC_NAME
|
176 G_PARAM_STATIC_NICK
| G_PARAM_STATIC_BLURB
));
180 yelp_transform_dispose (GObject
*object
)
182 YelpTransformPrivate
*priv
= GET_PRIV (object
);
184 debug_print (DB_FUNCTION
, "entering\n");
188 while ((chunk_id
= (gchar
*) g_async_queue_try_pop (priv
->queue
)))
190 g_async_queue_unref (priv
->queue
);
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,
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.
208 xsltFreeTransformContext (priv
->context
);
209 priv
->context
= NULL
;
211 if (priv
->stylesheet
) {
212 xsltFreeStylesheet (priv
->stylesheet
);
213 priv
->stylesheet
= NULL
;
216 xmlFreeDoc (priv
->output
);
220 G_OBJECT_CLASS (yelp_transform_parent_class
)->dispose (object
);
224 yelp_transform_finalize (GObject
*object
)
226 YelpTransformPrivate
*priv
= GET_PRIV (object
);
227 xsltDocumentPtr xsltdoc
;
231 debug_print (DB_FUNCTION
, "entering\n");
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
))
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
);
248 yelp_transform_get_property (GObject
*object
,
253 YelpTransformPrivate
*priv
= GET_PRIV (object
);
257 case PROP_STYLESHEET
:
258 g_value_set_string (value
, priv
->stylesheet_file
);
261 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
267 yelp_transform_set_property (GObject
*object
,
272 YelpTransformPrivate
*priv
= GET_PRIV (object
);
276 case PROP_STYLESHEET
:
277 priv
->stylesheet_file
= g_value_dup_string (value
);
280 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
285 /******************************************************************************/
288 yelp_transform_new (const gchar
*stylesheet
)
290 return (YelpTransform
*) g_object_new (YELP_TYPE_TRANSFORM
,
291 "stylesheet", stylesheet
,
296 yelp_transform_start (YelpTransform
*transform
,
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
);
319 yelp_transform_take_chunk (YelpTransform
*transform
,
320 const gchar
*chunk_id
)
322 YelpTransformPrivate
*priv
= GET_PRIV (transform
);
325 g_mutex_lock (priv
->mutex
);
327 buf
= g_hash_table_lookup (priv
->chunks
, chunk_id
);
329 g_hash_table_remove (priv
->chunks
, chunk_id
);
331 g_mutex_unlock (priv
->mutex
);
333 /* The caller assumes ownership of this memory. */
338 yelp_transform_cancel (YelpTransform
*transform
)
340 YelpTransformPrivate
*priv
= GET_PRIV (transform
);
341 g_mutex_lock (priv
->mutex
);
343 priv
->cancelled
= TRUE
;
345 priv
->context
->state
= XSLT_STATE_STOPPED
;
347 g_mutex_unlock (priv
->mutex
);
351 yelp_transform_get_error (YelpTransform
*transform
)
353 YelpTransformPrivate
*priv
= GET_PRIV (transform
);
356 g_mutex_lock (priv
->mutex
);
358 ret
= g_error_copy (priv
->error
);
359 g_mutex_unlock (priv
->mutex
);
364 /******************************************************************************/
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
);
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
);
387 priv
->context
= xsltNewTransformContext (priv
->stylesheet
,
389 if (priv
->context
== NULL
) {
390 g_mutex_lock (priv
->mutex
);
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
);
402 priv
->context
->_private
= transform
;
403 xsltRegisterExtElement (priv
->context
,
405 BAD_CAST YELP_NAMESPACE
,
406 (xsltTransformFunction
) xslt_yelp_document
);
407 xsltRegisterExtElement (priv
->context
,
409 BAD_CAST YELP_NAMESPACE
,
410 (xsltTransformFunction
) xslt_yelp_cache
);
411 xsltRegisterExtFunction (priv
->context
,
413 BAD_CAST YELP_NAMESPACE
,
414 (xmlXPathFunction
) xslt_yelp_aux
);
416 priv
->output
= xsltApplyStylesheetUser (priv
->stylesheet
,
418 (const char **) priv
->params
,
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
);
428 g_mutex_unlock (priv
->mutex
);
429 g_object_unref (transform
);
434 transform_chunk (YelpTransform
*transform
)
436 YelpTransformPrivate
*priv
= GET_PRIV (transform
);
439 debug_print (DB_FUNCTION
, "entering\n");
444 chunk_id
= (gchar
*) g_async_queue_try_pop (priv
->queue
);
446 g_signal_emit (transform
, signals
[CHUNK_READY
], 0, chunk_id
);
451 g_object_unref (transform
);
456 transform_error (YelpTransform
*transform
)
458 YelpTransformPrivate
*priv
= GET_PRIV (transform
);
460 debug_print (DB_FUNCTION
, "entering\n");
465 g_signal_emit (transform
, signals
[ERROR
], 0);
468 g_object_unref (transform
);
473 transform_final (YelpTransform
*transform
)
475 YelpTransformPrivate
*priv
= GET_PRIV (transform
);
477 debug_print (DB_FUNCTION
, "entering\n");
482 g_signal_emit (transform
, signals
[FINISHED
], 0);
485 g_object_unref (transform
);
489 /******************************************************************************/
492 xslt_yelp_document (xsltTransformContextPtr ctxt
,
495 xsltStylePreCompPtr comp
)
497 YelpTransform
*transform
;
498 YelpTransformPrivate
*priv
;
499 xmlChar
*page_id
= NULL
;
503 xsltStylesheetPtr style
= NULL
;
504 const char *old_outfile
;
505 xmlDocPtr new_doc
= NULL
;
507 xmlNodePtr old_insert
;
509 debug_print (DB_FUNCTION
, "entering\n");
511 if (ctxt
->state
== XSLT_STATE_STOPPED
)
514 if (!ctxt
|| !node
|| !inst
|| !comp
)
517 transform
= YELP_TRANSFORM (ctxt
->_private
);
518 priv
= GET_PRIV (transform
);
520 page_id
= xsltEvalAttrValueTemplate (ctxt
, inst
,
521 (const xmlChar
*) "href",
523 if (page_id
== NULL
|| *page_id
== '\0') {
527 xsltTransformError (ctxt
, NULL
, inst
,
528 _("No href attribute found on "
530 /* FIXME: put a real error here */
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 ();
542 xsltTransformError (ctxt
, NULL
, inst
,
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
);
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
);
579 xmlFreeDoc (new_doc
);
581 xsltFreeStylesheet (style
);
585 xslt_yelp_cache (xsltTransformContextPtr ctxt
,
588 xsltStylePreCompPtr comp
)
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
);