[libyelp/yelp-mallard-document] Support for Mallard Facets extension
[yelp.git] / libyelp / yelp-man-document.c
blob14ac8cd08737d8d74e55eea39c154d612b685c13
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2007-2010 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 <gtk/gtk.h>
30 #include <libxml/tree.h>
32 #include "yelp-error.h"
33 #include "yelp-man-document.h"
34 #include "yelp-man-parser.h"
35 #include "yelp-transform.h"
36 #include "yelp-settings.h"
38 #define STYLESHEET DATADIR"/yelp/xslt/man2html.xsl"
40 typedef enum {
41 MAN_STATE_BLANK, /* Brand new, run transform as needed */
42 MAN_STATE_PARSING, /* Parsing/transforming document, please wait */
43 MAN_STATE_PARSED, /* All done, if we ain't got it, it ain't here */
44 MAN_STATE_STOP /* Stop everything now, object to be disposed */
45 } ManState;
47 typedef struct _YelpManDocumentPrivate YelpManDocumentPrivate;
48 struct _YelpManDocumentPrivate {
49 YelpUri *uri;
50 ManState state;
52 GMutex *mutex;
53 GThread *thread;
55 xmlDocPtr xmldoc;
57 gboolean process_running;
58 gboolean transform_running;
60 YelpTransform *transform;
61 guint chunk_ready;
62 guint finished;
63 guint error;
66 typedef struct _YelpLangEncodings YelpLangEncodings;
67 struct _YelpLangEncodings {
68 gchar *language;
69 gchar *encoding;
71 /* http://www.w3.org/International/O-charset-lang.html */
72 static const YelpLangEncodings langmap[] = {
73 { "C", "ISO-8859-1" },
74 { "af", "ISO-8859-1" },
75 { "ar", "ISO-8859-6" },
76 { "bg", "ISO-8859-5" },
77 { "be", "ISO-8859-5" },
78 { "ca", "ISO-8859-1" },
79 { "cs", "ISO-8859-2" },
80 { "da", "ISO-8859-1" },
81 { "de", "ISO-8859-1" },
82 { "el", "ISO-8859-7" },
83 { "en", "ISO-8859-1" },
84 { "eo", "ISO-8859-3" },
85 { "es", "ISO-8859-1" },
86 { "et", "ISO-8859-15" },
87 { "eu", "ISO-8859-1" },
88 { "fi", "ISO-8859-1" },
89 { "fo", "ISO-8859-1" },
90 { "fr", "ISO-8859-1" },
91 { "ga", "ISO-8859-1" },
92 { "gd", "ISO-8859-1" },
93 { "gl", "ISO-8859-1" },
94 { "hu", "ISO-8859-2" },
95 { "id", "ISO-8859-1" }, /* is this right */
96 { "mt", "ISO-8859-3" },
97 { "is", "ISO-8859-1" },
98 { "it", "ISO-8859-1" },
99 { "iw", "ISO-8859-8" },
100 { "ja", "EUC-JP" },
101 { "ko", "EUC-KR" },
102 { "lt", "ISO-8859-13" },
103 { "lv", "ISO-8859-13" },
104 { "mk", "ISO-8859-5" },
105 { "mt", "ISO-8859-3" },
106 { "no", "ISO-8859-1" },
107 { "pl", "ISO-8859-2" },
108 { "pt_BR", "ISO-8859-1" },
109 { "ro", "ISO-8859-2" },
110 { "ru", "KOI8-R" },
111 { "sl", "ISO-8859-2" },
112 { "sr", "ISO-8859-2" }, /* Latin, not cyrillic */
113 { "sk", "ISO-8859-2" },
114 { "sv", "ISO-8859-1" },
115 { "tr", "ISO-8859-9" },
116 { "uk", "ISO-8859-5" },
117 { "zh_CN", "BIG5" },
118 { "zh_TW", "BIG5" },
119 { NULL, NULL },
122 static void yelp_man_document_class_init (YelpManDocumentClass *klass);
123 static void yelp_man_document_init (YelpManDocument *man);
124 static void yelp_man_document_dispose (GObject *object);
125 static void yelp_man_document_finalize (GObject *object);
127 /* YelpDocument */
128 static gboolean man_request_page (YelpDocument *document,
129 const gchar *page_id,
130 GCancellable *cancellable,
131 YelpDocumentCallback callback,
132 gpointer user_data);
134 /* YelpTransform */
135 static void transform_chunk_ready (YelpTransform *transform,
136 gchar *chunk_id,
137 YelpManDocument *man);
138 static void transform_finished (YelpTransform *transform,
139 YelpManDocument *man);
140 static void transform_error (YelpTransform *transform,
141 YelpManDocument *man);
142 static void transform_finalized (YelpManDocument *man,
143 gpointer transform);
145 /* Threaded */
146 static void man_document_process (YelpManDocument *man);
148 static void man_document_disconnect (YelpManDocument *man);
151 G_DEFINE_TYPE (YelpManDocument, yelp_man_document, YELP_TYPE_DOCUMENT);
152 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_MAN_DOCUMENT, YelpManDocumentPrivate))
154 static void
155 yelp_man_document_class_init (YelpManDocumentClass *klass)
157 GObjectClass *object_class = G_OBJECT_CLASS (klass);
158 YelpDocumentClass *document_class = YELP_DOCUMENT_CLASS (klass);
160 object_class->dispose = yelp_man_document_dispose;
161 object_class->finalize = yelp_man_document_finalize;
163 document_class->request_page = man_request_page;
165 g_type_class_add_private (klass, sizeof (YelpManDocumentPrivate));
168 static void
169 yelp_man_document_init (YelpManDocument *man)
171 YelpManDocumentPrivate *priv = GET_PRIV (man);
173 priv->state = MAN_STATE_BLANK;
174 priv->mutex = g_mutex_new ();
177 static void
178 yelp_man_document_dispose (GObject *object)
180 YelpManDocumentPrivate *priv = GET_PRIV (object);
182 if (priv->uri) {
183 g_object_unref (priv->uri);
184 priv->uri = NULL;
187 G_OBJECT_CLASS (yelp_man_document_parent_class)->dispose (object);
190 static void
191 yelp_man_document_finalize (GObject *object)
193 YelpManDocumentPrivate *priv = GET_PRIV (object);
195 if (priv->xmldoc)
196 xmlFreeDoc (priv->xmldoc);
198 g_mutex_free (priv->mutex);
200 G_OBJECT_CLASS (yelp_man_document_parent_class)->finalize (object);
203 /******************************************************************************/
205 YelpDocument *
206 yelp_man_document_new (YelpUri *uri)
208 YelpManDocument *man;
209 YelpManDocumentPrivate *priv;
211 g_return_val_if_fail (uri != NULL, NULL);
213 man = (YelpManDocument *) g_object_new (YELP_TYPE_MAN_DOCUMENT, NULL);
214 priv = GET_PRIV (man);
216 priv->uri = g_object_ref (uri);
218 return (YelpDocument *) man;
222 /******************************************************************************/
223 /** YelpDocument **************************************************************/
225 static gboolean
226 man_request_page (YelpDocument *document,
227 const gchar *page_id,
228 GCancellable *cancellable,
229 YelpDocumentCallback callback,
230 gpointer user_data)
232 YelpManDocumentPrivate *priv = GET_PRIV (document);
233 gchar *docuri;
234 GError *error;
235 gboolean handled;
237 if (page_id == NULL)
238 page_id = "//index";
240 handled =
241 YELP_DOCUMENT_CLASS (yelp_man_document_parent_class)->request_page (document,
242 page_id,
243 cancellable,
244 callback,
245 user_data);
246 if (handled) {
247 return;
250 g_mutex_lock (priv->mutex);
252 switch (priv->state) {
253 case MAN_STATE_BLANK:
254 priv->state = MAN_STATE_PARSING;
255 priv->process_running = TRUE;
256 g_object_ref (document);
257 yelp_document_set_page_id (document, NULL, "//index");
258 yelp_document_set_page_id (document, "//index", "//index");
259 yelp_document_set_root_id (document, "//index", "//index");
260 priv->thread = g_thread_create ((GThreadFunc) man_document_process,
261 document, FALSE, NULL);
262 break;
263 case MAN_STATE_PARSING:
264 break;
265 case MAN_STATE_PARSED:
266 case MAN_STATE_STOP:
267 docuri = yelp_uri_get_document_uri (priv->uri);
268 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
269 _("The page ‘%s’ was not found in the document ‘%s’."),
270 page_id, docuri);
271 g_free (docuri);
272 yelp_document_signal (document, page_id,
273 YELP_DOCUMENT_SIGNAL_ERROR,
274 error);
275 g_error_free (error);
276 break;
279 g_mutex_unlock (priv->mutex);
283 /******************************************************************************/
284 /** YelpTransform *************************************************************/
286 static void
287 transform_chunk_ready (YelpTransform *transform,
288 gchar *chunk_id,
289 YelpManDocument *man)
291 YelpManDocumentPrivate *priv = GET_PRIV (man);
292 gchar *content;
294 g_assert (transform == priv->transform);
296 if (priv->state == MAN_STATE_STOP) {
297 man_document_disconnect (man);
298 return;
301 content = yelp_transform_take_chunk (transform, chunk_id);
302 yelp_document_give_contents (YELP_DOCUMENT (man),
303 chunk_id,
304 content,
305 "application/xhtml+xml");
307 yelp_document_signal (YELP_DOCUMENT (man),
308 chunk_id,
309 YELP_DOCUMENT_SIGNAL_INFO,
310 NULL);
311 yelp_document_signal (YELP_DOCUMENT (man),
312 chunk_id,
313 YELP_DOCUMENT_SIGNAL_CONTENTS,
314 NULL);
317 static void
318 transform_finished (YelpTransform *transform,
319 YelpManDocument *man)
321 YelpManDocumentPrivate *priv = GET_PRIV (man);
322 gchar *docuri;
323 GError *error;
325 g_assert (transform == priv->transform);
327 if (priv->state == MAN_STATE_STOP) {
328 man_document_disconnect (man);
329 return;
332 man_document_disconnect (man);
333 priv->state = MAN_STATE_PARSED;
335 /* We want to free priv->xmldoc, but we can't free it before transform
336 is finalized. Otherwise, we could crash when YelpTransform frees
337 its libxslt resources.
339 g_object_weak_ref ((GObject *) transform,
340 (GWeakNotify) transform_finalized,
341 man);
343 docuri = yelp_uri_get_document_uri (priv->uri);
344 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
345 _("The requested page was not found in the document ‘%s’."),
346 docuri);
347 g_free (docuri);
348 yelp_document_error_pending ((YelpDocument *) man, error);
349 g_error_free (error);
352 static void
353 transform_error (YelpTransform *transform,
354 YelpManDocument *man)
356 YelpManDocumentPrivate *priv = GET_PRIV (man);
357 GError *error;
359 g_assert (transform == priv->transform);
361 if (priv->state == MAN_STATE_STOP) {
362 man_document_disconnect (man);
363 return;
366 error = yelp_transform_get_error (transform);
367 yelp_document_error_pending ((YelpDocument *) man, error);
368 g_error_free (error);
370 man_document_disconnect (man);
373 static void
374 transform_finalized (YelpManDocument *man,
375 gpointer transform)
377 YelpManDocumentPrivate *priv = GET_PRIV (man);
379 if (priv->xmldoc)
380 xmlFreeDoc (priv->xmldoc);
381 priv->xmldoc = NULL;
385 /******************************************************************************/
386 /** Threaded ******************************************************************/
388 static void
389 man_document_process (YelpManDocument *man)
391 YelpManDocumentPrivate *priv = GET_PRIV (man);
392 GFile *file = NULL;
393 gchar *filepath = NULL;
394 GError *error;
395 gint params_i = 0;
396 gchar **params = NULL;
397 YelpManParser *parser;
398 const gchar *language, *encoding;
400 file = yelp_uri_get_file (priv->uri);
401 if (file == NULL) {
402 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
403 _("The file does not exist."));
404 yelp_document_error_pending ((YelpDocument *) man, error);
405 g_error_free (error);
406 goto done;
409 filepath = g_file_get_path (file);
410 g_object_unref (file);
411 if (!g_file_test (filepath, G_FILE_TEST_IS_REGULAR)) {
412 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
413 _("The file ‘%s’ does not exist."),
414 filepath);
415 yelp_document_error_pending ((YelpDocument *) man, error);
416 g_error_free (error);
417 goto done;
420 /* FIXME: get the language */
421 language = "C";
423 /* default encoding if the language doesn't match below */
424 encoding = g_getenv("MAN_ENCODING");
425 if (encoding == NULL)
426 encoding = "ISO-8859-1";
428 if (language != NULL) {
429 gint i;
430 for (i = 0; langmap[i].language != NULL; i++) {
431 if (g_str_equal (language, langmap[i].language)) {
432 encoding = langmap[i].encoding;
433 break;
438 parser = yelp_man_parser_new ();
439 priv->xmldoc = yelp_man_parser_parse_file (parser, filepath, encoding);
440 yelp_man_parser_free (parser);
442 if (priv->xmldoc == NULL) {
443 error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING,
444 _("The file ‘%s’ could not be parsed because it is"
445 " not a well-formed man page."),
446 filepath);
447 yelp_document_error_pending ((YelpDocument *) man, error);
450 g_mutex_lock (priv->mutex);
451 if (priv->state == MAN_STATE_STOP) {
452 g_mutex_unlock (priv->mutex);
453 goto done;
456 priv->transform = yelp_transform_new (STYLESHEET);
457 priv->chunk_ready =
458 g_signal_connect (priv->transform, "chunk-ready",
459 (GCallback) transform_chunk_ready,
460 man);
461 priv->finished =
462 g_signal_connect (priv->transform, "finished",
463 (GCallback) transform_finished,
464 man);
465 priv->error =
466 g_signal_connect (priv->transform, "error",
467 (GCallback) transform_error,
468 man);
470 params = yelp_settings_get_all_params (yelp_settings_get_default (), 0, &params_i);
472 priv->transform_running = TRUE;
473 yelp_transform_start (priv->transform,
474 priv->xmldoc,
475 NULL,
476 (const gchar * const *) params);
477 g_strfreev (params);
478 g_mutex_unlock (priv->mutex);
480 done:
481 g_free (filepath);
482 priv->process_running = FALSE;
483 g_object_unref (man);
486 static void
487 man_document_disconnect (YelpManDocument *man)
489 YelpManDocumentPrivate *priv = GET_PRIV (man);
490 if (priv->chunk_ready) {
491 g_signal_handler_disconnect (priv->transform, priv->chunk_ready);
492 priv->chunk_ready = 0;
494 if (priv->finished) {
495 g_signal_handler_disconnect (priv->transform, priv->finished);
496 priv->finished = 0;
498 if (priv->error) {
499 g_signal_handler_disconnect (priv->transform, priv->error);
500 priv->error = 0;
502 yelp_transform_cancel (priv->transform);
503 g_object_unref (priv->transform);
504 priv->transform = NULL;
505 priv->transform_running = FALSE;