diff options
author | Shaun McCance <shaunm@gnome.org> | 2010-06-01 15:01:57 -0500 |
---|---|---|
committer | Shaun McCance <shaunm@gnome.org> | 2010-06-01 15:02:42 -0500 |
commit | 78dbed19c11400d1f9b182acc01a7414dadd191f (patch) | |
tree | f65a7e470ef1843598d68bcb0ca5466480cdd515 /libyelp/yelp-man-document.c | |
parent | 6d2f282102a2992c6197b60feed41d6a73272400 (diff) | |
download | yelp-78dbed19c11400d1f9b182acc01a7414dadd191f.tar.gz |
[libyelp] Adding man page support, some formatting still off
Diffstat (limited to 'libyelp/yelp-man-document.c')
-rw-r--r-- | libyelp/yelp-man-document.c | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/libyelp/yelp-man-document.c b/libyelp/yelp-man-document.c new file mode 100644 index 00000000..14ac8cd0 --- /dev/null +++ b/libyelp/yelp-man-document.c @@ -0,0 +1,506 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2007-2010 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 <gtk/gtk.h> +#include <libxml/tree.h> + +#include "yelp-error.h" +#include "yelp-man-document.h" +#include "yelp-man-parser.h" +#include "yelp-transform.h" +#include "yelp-settings.h" + +#define STYLESHEET DATADIR"/yelp/xslt/man2html.xsl" + +typedef enum { + MAN_STATE_BLANK, /* Brand new, run transform as needed */ + MAN_STATE_PARSING, /* Parsing/transforming document, please wait */ + MAN_STATE_PARSED, /* All done, if we ain't got it, it ain't here */ + MAN_STATE_STOP /* Stop everything now, object to be disposed */ +} ManState; + +typedef struct _YelpManDocumentPrivate YelpManDocumentPrivate; +struct _YelpManDocumentPrivate { + YelpUri *uri; + ManState state; + + GMutex *mutex; + GThread *thread; + + xmlDocPtr xmldoc; + + gboolean process_running; + gboolean transform_running; + + YelpTransform *transform; + guint chunk_ready; + guint finished; + guint error; +}; + +typedef struct _YelpLangEncodings YelpLangEncodings; +struct _YelpLangEncodings { + gchar *language; + gchar *encoding; +}; +/* http://www.w3.org/International/O-charset-lang.html */ +static const YelpLangEncodings langmap[] = { + { "C", "ISO-8859-1" }, + { "af", "ISO-8859-1" }, + { "ar", "ISO-8859-6" }, + { "bg", "ISO-8859-5" }, + { "be", "ISO-8859-5" }, + { "ca", "ISO-8859-1" }, + { "cs", "ISO-8859-2" }, + { "da", "ISO-8859-1" }, + { "de", "ISO-8859-1" }, + { "el", "ISO-8859-7" }, + { "en", "ISO-8859-1" }, + { "eo", "ISO-8859-3" }, + { "es", "ISO-8859-1" }, + { "et", "ISO-8859-15" }, + { "eu", "ISO-8859-1" }, + { "fi", "ISO-8859-1" }, + { "fo", "ISO-8859-1" }, + { "fr", "ISO-8859-1" }, + { "ga", "ISO-8859-1" }, + { "gd", "ISO-8859-1" }, + { "gl", "ISO-8859-1" }, + { "hu", "ISO-8859-2" }, + { "id", "ISO-8859-1" }, /* is this right */ + { "mt", "ISO-8859-3" }, + { "is", "ISO-8859-1" }, + { "it", "ISO-8859-1" }, + { "iw", "ISO-8859-8" }, + { "ja", "EUC-JP" }, + { "ko", "EUC-KR" }, + { "lt", "ISO-8859-13" }, + { "lv", "ISO-8859-13" }, + { "mk", "ISO-8859-5" }, + { "mt", "ISO-8859-3" }, + { "no", "ISO-8859-1" }, + { "pl", "ISO-8859-2" }, + { "pt_BR", "ISO-8859-1" }, + { "ro", "ISO-8859-2" }, + { "ru", "KOI8-R" }, + { "sl", "ISO-8859-2" }, + { "sr", "ISO-8859-2" }, /* Latin, not cyrillic */ + { "sk", "ISO-8859-2" }, + { "sv", "ISO-8859-1" }, + { "tr", "ISO-8859-9" }, + { "uk", "ISO-8859-5" }, + { "zh_CN", "BIG5" }, + { "zh_TW", "BIG5" }, + { NULL, NULL }, +}; + +static void yelp_man_document_class_init (YelpManDocumentClass *klass); +static void yelp_man_document_init (YelpManDocument *man); +static void yelp_man_document_dispose (GObject *object); +static void yelp_man_document_finalize (GObject *object); + +/* YelpDocument */ +static gboolean man_request_page (YelpDocument *document, + const gchar *page_id, + GCancellable *cancellable, + YelpDocumentCallback callback, + gpointer user_data); + +/* YelpTransform */ +static void transform_chunk_ready (YelpTransform *transform, + gchar *chunk_id, + YelpManDocument *man); +static void transform_finished (YelpTransform *transform, + YelpManDocument *man); +static void transform_error (YelpTransform *transform, + YelpManDocument *man); +static void transform_finalized (YelpManDocument *man, + gpointer transform); + +/* Threaded */ +static void man_document_process (YelpManDocument *man); + +static void man_document_disconnect (YelpManDocument *man); + + +G_DEFINE_TYPE (YelpManDocument, yelp_man_document, YELP_TYPE_DOCUMENT); +#define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_MAN_DOCUMENT, YelpManDocumentPrivate)) + +static void +yelp_man_document_class_init (YelpManDocumentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + YelpDocumentClass *document_class = YELP_DOCUMENT_CLASS (klass); + + object_class->dispose = yelp_man_document_dispose; + object_class->finalize = yelp_man_document_finalize; + + document_class->request_page = man_request_page; + + g_type_class_add_private (klass, sizeof (YelpManDocumentPrivate)); +} + +static void +yelp_man_document_init (YelpManDocument *man) +{ + YelpManDocumentPrivate *priv = GET_PRIV (man); + + priv->state = MAN_STATE_BLANK; + priv->mutex = g_mutex_new (); +} + +static void +yelp_man_document_dispose (GObject *object) +{ + YelpManDocumentPrivate *priv = GET_PRIV (object); + + if (priv->uri) { + g_object_unref (priv->uri); + priv->uri = NULL; + } + + G_OBJECT_CLASS (yelp_man_document_parent_class)->dispose (object); +} + +static void +yelp_man_document_finalize (GObject *object) +{ + YelpManDocumentPrivate *priv = GET_PRIV (object); + + if (priv->xmldoc) + xmlFreeDoc (priv->xmldoc); + + g_mutex_free (priv->mutex); + + G_OBJECT_CLASS (yelp_man_document_parent_class)->finalize (object); +} + +/******************************************************************************/ + +YelpDocument * +yelp_man_document_new (YelpUri *uri) +{ + YelpManDocument *man; + YelpManDocumentPrivate *priv; + + g_return_val_if_fail (uri != NULL, NULL); + + man = (YelpManDocument *) g_object_new (YELP_TYPE_MAN_DOCUMENT, NULL); + priv = GET_PRIV (man); + + priv->uri = g_object_ref (uri); + + return (YelpDocument *) man; +} + + +/******************************************************************************/ +/** YelpDocument **************************************************************/ + +static gboolean +man_request_page (YelpDocument *document, + const gchar *page_id, + GCancellable *cancellable, + YelpDocumentCallback callback, + gpointer user_data) +{ + YelpManDocumentPrivate *priv = GET_PRIV (document); + gchar *docuri; + GError *error; + gboolean handled; + + if (page_id == NULL) + page_id = "//index"; + + handled = + YELP_DOCUMENT_CLASS (yelp_man_document_parent_class)->request_page (document, + page_id, + cancellable, + callback, + user_data); + if (handled) { + return; + } + + g_mutex_lock (priv->mutex); + + switch (priv->state) { + case MAN_STATE_BLANK: + priv->state = MAN_STATE_PARSING; + priv->process_running = TRUE; + g_object_ref (document); + yelp_document_set_page_id (document, NULL, "//index"); + yelp_document_set_page_id (document, "//index", "//index"); + yelp_document_set_root_id (document, "//index", "//index"); + priv->thread = g_thread_create ((GThreadFunc) man_document_process, + document, FALSE, NULL); + break; + case MAN_STATE_PARSING: + break; + case MAN_STATE_PARSED: + case MAN_STATE_STOP: + docuri = yelp_uri_get_document_uri (priv->uri); + error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND, + _("The page ‘%s’ was not found in the document ‘%s’."), + page_id, docuri); + g_free (docuri); + yelp_document_signal (document, page_id, + YELP_DOCUMENT_SIGNAL_ERROR, + error); + g_error_free (error); + break; + } + + g_mutex_unlock (priv->mutex); +} + + +/******************************************************************************/ +/** YelpTransform *************************************************************/ + +static void +transform_chunk_ready (YelpTransform *transform, + gchar *chunk_id, + YelpManDocument *man) +{ + YelpManDocumentPrivate *priv = GET_PRIV (man); + gchar *content; + + g_assert (transform == priv->transform); + + if (priv->state == MAN_STATE_STOP) { + man_document_disconnect (man); + return; + } + + content = yelp_transform_take_chunk (transform, chunk_id); + yelp_document_give_contents (YELP_DOCUMENT (man), + chunk_id, + content, + "application/xhtml+xml"); + + yelp_document_signal (YELP_DOCUMENT (man), + chunk_id, + YELP_DOCUMENT_SIGNAL_INFO, + NULL); + yelp_document_signal (YELP_DOCUMENT (man), + chunk_id, + YELP_DOCUMENT_SIGNAL_CONTENTS, + NULL); +} + +static void +transform_finished (YelpTransform *transform, + YelpManDocument *man) +{ + YelpManDocumentPrivate *priv = GET_PRIV (man); + gchar *docuri; + GError *error; + + g_assert (transform == priv->transform); + + if (priv->state == MAN_STATE_STOP) { + man_document_disconnect (man); + return; + } + + man_document_disconnect (man); + priv->state = MAN_STATE_PARSED; + + /* We want to free priv->xmldoc, but we can't free it before transform + is finalized. Otherwise, we could crash when YelpTransform frees + its libxslt resources. + */ + g_object_weak_ref ((GObject *) transform, + (GWeakNotify) transform_finalized, + man); + + docuri = yelp_uri_get_document_uri (priv->uri); + error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND, + _("The requested page was not found in the document ‘%s’."), + docuri); + g_free (docuri); + yelp_document_error_pending ((YelpDocument *) man, error); + g_error_free (error); +} + +static void +transform_error (YelpTransform *transform, + YelpManDocument *man) +{ + YelpManDocumentPrivate *priv = GET_PRIV (man); + GError *error; + + g_assert (transform == priv->transform); + + if (priv->state == MAN_STATE_STOP) { + man_document_disconnect (man); + return; + } + + error = yelp_transform_get_error (transform); + yelp_document_error_pending ((YelpDocument *) man, error); + g_error_free (error); + + man_document_disconnect (man); +} + +static void +transform_finalized (YelpManDocument *man, + gpointer transform) +{ + YelpManDocumentPrivate *priv = GET_PRIV (man); + + if (priv->xmldoc) + xmlFreeDoc (priv->xmldoc); + priv->xmldoc = NULL; +} + + +/******************************************************************************/ +/** Threaded ******************************************************************/ + +static void +man_document_process (YelpManDocument *man) +{ + YelpManDocumentPrivate *priv = GET_PRIV (man); + GFile *file = NULL; + gchar *filepath = NULL; + GError *error; + gint params_i = 0; + gchar **params = NULL; + YelpManParser *parser; + const gchar *language, *encoding; + + file = yelp_uri_get_file (priv->uri); + if (file == NULL) { + error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND, + _("The file does not exist.")); + yelp_document_error_pending ((YelpDocument *) man, error); + g_error_free (error); + goto done; + } + + filepath = g_file_get_path (file); + g_object_unref (file); + if (!g_file_test (filepath, G_FILE_TEST_IS_REGULAR)) { + error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND, + _("The file ‘%s’ does not exist."), + filepath); + yelp_document_error_pending ((YelpDocument *) man, error); + g_error_free (error); + goto done; + } + + /* FIXME: get the language */ + language = "C"; + + /* default encoding if the language doesn't match below */ + encoding = g_getenv("MAN_ENCODING"); + if (encoding == NULL) + encoding = "ISO-8859-1"; + + if (language != NULL) { + gint i; + for (i = 0; langmap[i].language != NULL; i++) { + if (g_str_equal (language, langmap[i].language)) { + encoding = langmap[i].encoding; + break; + } + } + } + + parser = yelp_man_parser_new (); + priv->xmldoc = yelp_man_parser_parse_file (parser, filepath, encoding); + yelp_man_parser_free (parser); + + if (priv->xmldoc == NULL) { + error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING, + _("The file ‘%s’ could not be parsed because it is" + " not a well-formed man page."), + filepath); + yelp_document_error_pending ((YelpDocument *) man, error); + } + + g_mutex_lock (priv->mutex); + if (priv->state == MAN_STATE_STOP) { + g_mutex_unlock (priv->mutex); + goto done; + } + + priv->transform = yelp_transform_new (STYLESHEET); + priv->chunk_ready = + g_signal_connect (priv->transform, "chunk-ready", + (GCallback) transform_chunk_ready, + man); + priv->finished = + g_signal_connect (priv->transform, "finished", + (GCallback) transform_finished, + man); + priv->error = + g_signal_connect (priv->transform, "error", + (GCallback) transform_error, + man); + + params = yelp_settings_get_all_params (yelp_settings_get_default (), 0, ¶ms_i); + + priv->transform_running = TRUE; + yelp_transform_start (priv->transform, + priv->xmldoc, + NULL, + (const gchar * const *) params); + g_strfreev (params); + g_mutex_unlock (priv->mutex); + + done: + g_free (filepath); + priv->process_running = FALSE; + g_object_unref (man); +} + +static void +man_document_disconnect (YelpManDocument *man) +{ + YelpManDocumentPrivate *priv = GET_PRIV (man); + if (priv->chunk_ready) { + g_signal_handler_disconnect (priv->transform, priv->chunk_ready); + priv->chunk_ready = 0; + } + if (priv->finished) { + g_signal_handler_disconnect (priv->transform, priv->finished); + priv->finished = 0; + } + if (priv->error) { + g_signal_handler_disconnect (priv->transform, priv->error); + priv->error = 0; + } + yelp_transform_cancel (priv->transform); + g_object_unref (priv->transform); + priv->transform = NULL; + priv->transform_running = FALSE; +} |