Update Chinese (China) translation
[yelp.git] / libyelp / yelp-man-document.c
blobad7fab4f741e89e346e4c26ff4820e1f5e7b218d
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, see <http://www.gnu.org/licenses/>.
18 * Author: Shaun McCance <shaunm@gnome.org>
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <gtk/gtk.h>
28 #include <libxml/tree.h>
30 #include "yelp-error.h"
31 #include "yelp-man-document.h"
32 #include "yelp-man-parser.h"
33 #include "yelp-transform.h"
34 #include "yelp-settings.h"
36 #define STYLESHEET DATADIR"/yelp/xslt/man2html.xsl"
38 typedef enum {
39 MAN_STATE_BLANK, /* Brand new, run transform as needed */
40 MAN_STATE_PARSING, /* Parsing/transforming document, please wait */
41 MAN_STATE_PARSED, /* All done, if we ain't got it, it ain't here */
42 MAN_STATE_STOP /* Stop everything now, object to be disposed */
43 } ManState;
45 typedef struct _YelpManDocumentPrivate YelpManDocumentPrivate;
46 struct _YelpManDocumentPrivate {
47 ManState state;
48 gchar *page_id;
50 GMutex mutex;
51 GThread *thread;
53 xmlDocPtr xmldoc;
55 gboolean process_running;
56 gboolean transform_running;
58 YelpTransform *transform;
59 guint chunk_ready;
60 guint finished;
61 guint error;
64 typedef struct _YelpLangEncodings YelpLangEncodings;
65 struct _YelpLangEncodings {
66 const gchar *language;
67 const gchar *encoding;
69 /* http://www.w3.org/International/O-charset-lang.html */
70 static const YelpLangEncodings langmap[] = {
71 { "C", "ISO-8859-1" },
72 { "af", "ISO-8859-1" },
73 { "ar", "ISO-8859-6" },
74 { "bg", "ISO-8859-5" },
75 { "be", "ISO-8859-5" },
76 { "ca", "ISO-8859-1" },
77 { "cs", "ISO-8859-2" },
78 { "da", "ISO-8859-1" },
79 { "de", "ISO-8859-1" },
80 { "el", "ISO-8859-7" },
81 { "en", "ISO-8859-1" },
82 { "eo", "ISO-8859-3" },
83 { "es", "ISO-8859-1" },
84 { "et", "ISO-8859-15" },
85 { "eu", "ISO-8859-1" },
86 { "fi", "ISO-8859-1" },
87 { "fo", "ISO-8859-1" },
88 { "fr", "ISO-8859-1" },
89 { "ga", "ISO-8859-1" },
90 { "gd", "ISO-8859-1" },
91 { "gl", "ISO-8859-1" },
92 { "hu", "ISO-8859-2" },
93 { "id", "ISO-8859-1" }, /* is this right */
94 { "mt", "ISO-8859-3" },
95 { "is", "ISO-8859-1" },
96 { "it", "ISO-8859-1" },
97 { "iw", "ISO-8859-8" },
98 { "ja", "EUC-JP" },
99 { "ko", "EUC-KR" },
100 { "lt", "ISO-8859-13" },
101 { "lv", "ISO-8859-13" },
102 { "mk", "ISO-8859-5" },
103 { "mt", "ISO-8859-3" },
104 { "no", "ISO-8859-1" },
105 { "pl", "ISO-8859-2" },
106 { "pt_BR", "ISO-8859-1" },
107 { "ro", "ISO-8859-2" },
108 { "ru", "KOI8-R" },
109 { "sl", "ISO-8859-2" },
110 { "sr", "ISO-8859-2" }, /* Latin, not cyrillic */
111 { "sk", "ISO-8859-2" },
112 { "sv", "ISO-8859-1" },
113 { "tr", "ISO-8859-9" },
114 { "uk", "ISO-8859-5" },
115 { "zh_CN", "BIG5" },
116 { "zh_TW", "BIG5" },
117 { NULL, NULL },
120 static void yelp_man_document_finalize (GObject *object);
122 /* YelpDocument */
123 static gboolean man_request_page (YelpDocument *document,
124 const gchar *page_id,
125 GCancellable *cancellable,
126 YelpDocumentCallback callback,
127 gpointer user_data,
128 GDestroyNotify notify);
130 /* YelpTransform */
131 static void transform_chunk_ready (YelpTransform *transform,
132 gchar *chunk_id,
133 YelpManDocument *man);
134 static void transform_finished (YelpTransform *transform,
135 YelpManDocument *man);
136 static void transform_error (YelpTransform *transform,
137 YelpManDocument *man);
138 static void transform_finalized (YelpManDocument *man,
139 gpointer transform);
141 /* Threaded */
142 static void man_document_process (YelpManDocument *man);
144 static void man_document_disconnect (YelpManDocument *man);
147 G_DEFINE_TYPE (YelpManDocument, yelp_man_document, YELP_TYPE_DOCUMENT)
148 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_MAN_DOCUMENT, YelpManDocumentPrivate))
150 static void
151 yelp_man_document_class_init (YelpManDocumentClass *klass)
153 GObjectClass *object_class = G_OBJECT_CLASS (klass);
154 YelpDocumentClass *document_class = YELP_DOCUMENT_CLASS (klass);
156 object_class->finalize = yelp_man_document_finalize;
158 document_class->request_page = man_request_page;
160 g_type_class_add_private (klass, sizeof (YelpManDocumentPrivate));
163 static void
164 yelp_man_document_init (YelpManDocument *man)
166 YelpManDocumentPrivate *priv = GET_PRIV (man);
168 priv->state = MAN_STATE_BLANK;
169 g_mutex_init (&priv->mutex);
172 static void
173 yelp_man_document_finalize (GObject *object)
175 YelpManDocumentPrivate *priv = GET_PRIV (object);
177 if (priv->xmldoc)
178 xmlFreeDoc (priv->xmldoc);
180 g_mutex_clear (&priv->mutex);
181 g_free (priv->page_id);
183 G_OBJECT_CLASS (yelp_man_document_parent_class)->finalize (object);
186 /******************************************************************************/
188 YelpDocument *
189 yelp_man_document_new (YelpUri *uri)
191 g_return_val_if_fail (uri != NULL, NULL);
193 return (YelpDocument *) g_object_new (YELP_TYPE_MAN_DOCUMENT,
194 "document-uri", uri,
195 NULL);
199 /******************************************************************************/
200 /** YelpDocument **************************************************************/
202 static gboolean
203 man_request_page (YelpDocument *document,
204 const gchar *page_id,
205 GCancellable *cancellable,
206 YelpDocumentCallback callback,
207 gpointer user_data,
208 GDestroyNotify notify)
210 YelpManDocumentPrivate *priv = GET_PRIV (document);
211 gchar *docuri, *fulluri;
212 GError *error;
213 gboolean handled;
215 fulluri = yelp_uri_get_canonical_uri (yelp_document_get_uri (document));
216 if (g_str_has_prefix (fulluri, "man:"))
217 priv->page_id = g_strdup (fulluri + 4);
218 else
219 priv->page_id = g_strdup ("//index");
220 g_free (fulluri);
222 handled =
223 YELP_DOCUMENT_CLASS (yelp_man_document_parent_class)->request_page (document,
224 page_id,
225 cancellable,
226 callback,
227 user_data,
228 notify);
229 if (handled) {
230 return handled;
233 g_mutex_lock (&priv->mutex);
235 switch (priv->state) {
236 case MAN_STATE_BLANK:
237 priv->state = MAN_STATE_PARSING;
238 priv->process_running = TRUE;
239 g_object_ref (document);
240 yelp_document_set_page_id (document, page_id, priv->page_id);
241 yelp_document_set_page_id (document, NULL, priv->page_id);
242 yelp_document_set_page_id (document, "//index", priv->page_id);
243 yelp_document_set_page_id (document, priv->page_id, priv->page_id);
244 yelp_document_set_root_id (document, priv->page_id, priv->page_id);
245 priv->thread = g_thread_new ("man-page",
246 (GThreadFunc)(GCallback) man_document_process,
247 document);
248 break;
249 case MAN_STATE_PARSING:
250 break;
251 case MAN_STATE_PARSED:
252 case MAN_STATE_STOP:
253 docuri = yelp_uri_get_document_uri (yelp_document_get_uri (document));
254 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
255 _("The page ‘%s’ was not found in the document ‘%s’."),
256 page_id, docuri);
257 g_free (docuri);
258 yelp_document_signal (document, page_id,
259 YELP_DOCUMENT_SIGNAL_ERROR,
260 error);
261 g_error_free (error);
262 break;
263 default:
264 g_assert_not_reached ();
265 break;
268 g_mutex_unlock (&priv->mutex);
269 return FALSE;
273 /******************************************************************************/
274 /** YelpTransform *************************************************************/
276 static void
277 transform_chunk_ready (YelpTransform *transform,
278 gchar *chunk_id,
279 YelpManDocument *man)
281 YelpManDocumentPrivate *priv = GET_PRIV (man);
282 gchar *content;
284 g_assert (transform == priv->transform);
286 if (priv->state == MAN_STATE_STOP) {
287 man_document_disconnect (man);
288 return;
291 content = yelp_transform_take_chunk (transform, chunk_id);
292 yelp_document_give_contents (YELP_DOCUMENT (man),
293 priv->page_id,
294 content,
295 "application/xhtml+xml");
297 yelp_document_signal (YELP_DOCUMENT (man),
298 priv->page_id,
299 YELP_DOCUMENT_SIGNAL_INFO,
300 NULL);
301 yelp_document_signal (YELP_DOCUMENT (man),
302 priv->page_id,
303 YELP_DOCUMENT_SIGNAL_CONTENTS,
304 NULL);
307 static void
308 transform_finished (YelpTransform *transform,
309 YelpManDocument *man)
311 YelpManDocumentPrivate *priv = GET_PRIV (man);
312 gchar *docuri;
313 GError *error;
315 g_assert (transform == priv->transform);
317 if (priv->state == MAN_STATE_STOP) {
318 man_document_disconnect (man);
319 return;
322 man_document_disconnect (man);
323 priv->state = MAN_STATE_PARSED;
325 /* We want to free priv->xmldoc, but we can't free it before transform
326 is finalized. Otherwise, we could crash when YelpTransform frees
327 its libxslt resources.
329 g_object_weak_ref ((GObject *) transform,
330 (GWeakNotify) transform_finalized,
331 man);
333 docuri = yelp_uri_get_document_uri (yelp_document_get_uri ((YelpDocument *) man));
334 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
335 _("The requested page was not found in the document ‘%s’."),
336 docuri);
337 g_free (docuri);
338 yelp_document_error_pending ((YelpDocument *) man, error);
339 g_error_free (error);
342 static void
343 transform_error (YelpTransform *transform,
344 YelpManDocument *man)
346 YelpManDocumentPrivate *priv = GET_PRIV (man);
347 GError *error;
349 g_assert (transform == priv->transform);
351 if (priv->state == MAN_STATE_STOP) {
352 man_document_disconnect (man);
353 return;
356 error = yelp_transform_get_error (transform);
357 yelp_document_error_pending ((YelpDocument *) man, error);
358 g_error_free (error);
360 man_document_disconnect (man);
363 static void
364 transform_finalized (YelpManDocument *man,
365 gpointer transform)
367 YelpManDocumentPrivate *priv = GET_PRIV (man);
369 if (priv->xmldoc)
370 xmlFreeDoc (priv->xmldoc);
371 priv->xmldoc = NULL;
375 /******************************************************************************/
376 /** Threaded ******************************************************************/
378 static void
379 man_document_process (YelpManDocument *man)
381 YelpManDocumentPrivate *priv = GET_PRIV (man);
382 GFile *file = NULL;
383 gchar *filepath = NULL;
384 GError *error;
385 gint params_i = 0;
386 gchar **params = NULL;
387 YelpManParser *parser;
388 const gchar *language, *encoding;
390 file = yelp_uri_get_file (yelp_document_get_uri ((YelpDocument *) man));
391 if (file == NULL) {
392 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
393 _("The file does not exist."));
394 yelp_document_error_pending ((YelpDocument *) man, error);
395 g_error_free (error);
396 goto done;
399 filepath = g_file_get_path (file);
400 g_object_unref (file);
401 if (!g_file_test (filepath, G_FILE_TEST_IS_REGULAR)) {
402 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
403 _("The file ‘%s’ does not exist."),
404 filepath);
405 yelp_document_error_pending ((YelpDocument *) man, error);
406 g_error_free (error);
407 goto done;
410 /* FIXME: get the language */
411 language = "C";
413 /* default encoding if the language doesn't match below */
414 encoding = g_getenv("MAN_ENCODING");
415 if (encoding == NULL)
416 encoding = "ISO-8859-1";
418 if (language != NULL) {
419 gint i;
420 for (i = 0; langmap[i].language != NULL; i++) {
421 if (g_str_equal (language, langmap[i].language)) {
422 encoding = langmap[i].encoding;
423 break;
428 parser = yelp_man_parser_new ();
429 priv->xmldoc = yelp_man_parser_parse_file (parser, filepath, &error);
430 yelp_man_parser_free (parser);
432 if (priv->xmldoc == NULL) {
433 yelp_document_error_pending ((YelpDocument *) man, error);
436 g_mutex_lock (&priv->mutex);
437 if (priv->state == MAN_STATE_STOP) {
438 g_mutex_unlock (&priv->mutex);
439 goto done;
442 priv->transform = yelp_transform_new (STYLESHEET);
443 priv->chunk_ready =
444 g_signal_connect (priv->transform, "chunk-ready",
445 (GCallback) transform_chunk_ready,
446 man);
447 priv->finished =
448 g_signal_connect (priv->transform, "finished",
449 (GCallback) transform_finished,
450 man);
451 priv->error =
452 g_signal_connect (priv->transform, "error",
453 (GCallback) transform_error,
454 man);
456 params = yelp_settings_get_all_params (yelp_settings_get_default (), 0, &params_i);
458 priv->transform_running = TRUE;
459 yelp_transform_start (priv->transform,
460 priv->xmldoc,
461 NULL,
462 (const gchar * const *) params);
463 g_strfreev (params);
464 g_mutex_unlock (&priv->mutex);
466 done:
467 g_free (filepath);
468 priv->process_running = FALSE;
469 g_object_unref (man);
472 static void
473 man_document_disconnect (YelpManDocument *man)
475 YelpManDocumentPrivate *priv = GET_PRIV (man);
476 if (priv->chunk_ready) {
477 g_signal_handler_disconnect (priv->transform, priv->chunk_ready);
478 priv->chunk_ready = 0;
480 if (priv->finished) {
481 g_signal_handler_disconnect (priv->transform, priv->finished);
482 priv->finished = 0;
484 if (priv->error) {
485 g_signal_handler_disconnect (priv->transform, priv->error);
486 priv->error = 0;
488 yelp_transform_cancel (priv->transform);
489 g_object_unref (priv->transform);
490 priv->transform = NULL;
491 priv->transform_running = FALSE;