/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * Copyright (C) 2009-2020 Shaun McCance * * 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, see . * * Author: Shaun McCance */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "yelp-error.h" #include "yelp-mallard-document.h" #include "yelp-settings.h" #include "yelp-storage.h" #include "yelp-transform.h" #include "yelp-debug.h" #define STYLESHEET DATADIR"/yelp/xslt/mal2html.xsl" #define MALLARD_NS BAD_CAST "http://projectmallard.org/1.0/" #define CACHE_NS BAD_CAST "http://projectmallard.org/cache/1.0/" typedef enum { MALLARD_STATE_BLANK, MALLARD_STATE_THINKING, MALLARD_STATE_IDLE, MALLARD_STATE_STOP } MallardState; typedef struct { YelpMallardDocument *mallard; gchar *page_id; gchar *filename; xmlDocPtr xmldoc; YelpTransform *transform; guint chunk_ready; guint finished; guint error; xmlNodePtr cur; xmlNodePtr cache; xmlXPathContextPtr xpath; gboolean link_title; gboolean sort_title; gchar *page_title; gchar *page_desc; gchar *page_keywords; gchar *next_page; } MallardPageData; static void yelp_mallard_document_dispose (GObject *object); static void yelp_mallard_document_finalize (GObject *object); static void mallard_index (YelpDocument *document); static gboolean mallard_request_page (YelpDocument *document, const gchar *page_id, GCancellable *cancellable, YelpDocumentCallback callback, gpointer user_data, GDestroyNotify notify); static void transform_chunk_ready (YelpTransform *transform, gchar *chunk_id, MallardPageData *page_data); static void transform_finished (YelpTransform *transform, MallardPageData *page_data); static void transform_error (YelpTransform *transform, MallardPageData *page_data); static void mallard_think (YelpMallardDocument *mallard); static void mallard_try_run (YelpMallardDocument *mallard, const gchar *page_id); static void mallard_page_data_cancel (MallardPageData *page_data); static void mallard_page_data_walk (MallardPageData *page_data); static void mallard_page_data_info (MallardPageData *page_data, xmlNodePtr info_node, xmlNodePtr cache_node); static void mallard_page_data_run (MallardPageData *page_data); static void mallard_page_data_free (MallardPageData *page_data); static void mallard_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, YelpMallardDocument *mallard); static const char * xml_node_get_icon (xmlNodePtr node); static gboolean xml_node_is_ns_name (xmlNodePtr node, const xmlChar *ns, const xmlChar *name); typedef struct _YelpMallardDocumentPrivate YelpMallardDocumentPrivate; struct _YelpMallardDocumentPrivate { MallardState state; GMutex mutex; GThread *thread; gboolean thread_running; GThread *index; gboolean index_running; GSList *pending; xmlDocPtr cache; xmlNsPtr mallard_ns; xmlNsPtr cache_ns; GHashTable *pages_hash; GFileMonitor **monitors; xmlXPathCompExprPtr normalize; }; G_DEFINE_TYPE_WITH_PRIVATE (YelpMallardDocument, yelp_mallard_document, YELP_TYPE_DOCUMENT) /******************************************************************************/ static void yelp_mallard_document_class_init (YelpMallardDocumentClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); YelpDocumentClass *document_class = YELP_DOCUMENT_CLASS (klass); object_class->dispose = yelp_mallard_document_dispose; object_class->finalize = yelp_mallard_document_finalize; document_class->request_page = mallard_request_page; document_class->index = mallard_index; } static void yelp_mallard_document_init (YelpMallardDocument *mallard) { YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (mallard); xmlNodePtr cur; g_mutex_init (&priv->mutex); priv->thread_running = FALSE; priv->index_running = FALSE; priv->cache = xmlNewDoc (BAD_CAST "1.0"); priv->mallard_ns = xmlNewNs (NULL, MALLARD_NS, BAD_CAST "mal"); priv->cache_ns = xmlNewNs (NULL, CACHE_NS, BAD_CAST "cache"); cur = xmlNewDocNode (priv->cache, priv->cache_ns, BAD_CAST "cache", NULL); xmlDocSetRootElement (priv->cache, cur); priv->cache_ns->next = priv->mallard_ns; priv->mallard_ns->next = cur->nsDef; cur->nsDef = priv->cache_ns; priv->pages_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) mallard_page_data_free); priv->normalize = xmlXPathCompile (BAD_CAST "normalize-space(.)"); } static void yelp_mallard_document_dispose (GObject *object) { gint i; YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (YELP_MALLARD_DOCUMENT (object)); if (priv->monitors != NULL) { for (i = 0; priv->monitors[i]; i++) { g_object_unref (priv->monitors[i]); } g_free (priv->monitors); priv->monitors = NULL; } G_OBJECT_CLASS (yelp_mallard_document_parent_class)->dispose (object); } static void yelp_mallard_document_finalize (GObject *object) { YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (YELP_MALLARD_DOCUMENT (object)); g_mutex_clear (&priv->mutex); g_hash_table_destroy (priv->pages_hash); xmlFreeDoc (priv->cache); if (priv->normalize) xmlXPathFreeCompExpr (priv->normalize); G_OBJECT_CLASS (yelp_mallard_document_parent_class)->finalize (object); } /******************************************************************************/ YelpDocument * yelp_mallard_document_new (YelpUri *uri) { YelpMallardDocument *mallard; YelpMallardDocumentPrivate *priv; gchar **path; gint path_i; g_return_val_if_fail (uri != NULL, NULL); mallard = (YelpMallardDocument *) g_object_new (YELP_TYPE_MALLARD_DOCUMENT, "document-uri", uri, NULL); priv = yelp_mallard_document_get_instance_private (mallard); yelp_document_set_page_id ((YelpDocument *) mallard, NULL, "index"); yelp_document_set_page_id ((YelpDocument *) mallard, "index", "index"); path = yelp_uri_get_search_path (uri); priv->monitors = g_new0 (GFileMonitor*, g_strv_length (path) + 1); for (path_i = 0; path[path_i]; path_i++) { GFile *file; file = g_file_new_for_path (path[path_i]); priv->monitors[path_i] = g_file_monitor (file, G_FILE_MONITOR_SEND_MOVED, NULL, NULL); g_signal_connect (priv->monitors[path_i], "changed", G_CALLBACK (mallard_monitor_changed), mallard); g_object_unref (file); } g_strfreev (path); return (YelpDocument *) mallard; } static gboolean mallard_request_page (YelpDocument *document, const gchar *page_id, GCancellable *cancellable, YelpDocumentCallback callback, gpointer user_data, GDestroyNotify notify) { YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (YELP_MALLARD_DOCUMENT (document)); gchar *docuri; GError *error; gboolean handled; debug_print (DB_FUNCTION, "entering\n"); debug_print (DB_ARG, " page_id=\"%s\"\n", page_id); if (page_id == NULL) page_id = "index"; handled = YELP_DOCUMENT_CLASS (yelp_mallard_document_parent_class)->request_page (document, page_id, cancellable, callback, user_data, notify); if (handled) { return TRUE; } g_mutex_lock (&priv->mutex); if (priv->state == MALLARD_STATE_BLANK) { priv->state = MALLARD_STATE_THINKING; priv->thread_running = TRUE; g_object_ref (document); priv->thread = g_thread_new ("mallard-page", (GThreadFunc)(GCallback) mallard_think, document); } switch (priv->state) { case MALLARD_STATE_THINKING: priv->pending = g_slist_prepend (priv->pending, (gpointer) g_strdup (page_id)); break; case MALLARD_STATE_IDLE: mallard_try_run ((YelpMallardDocument *) document, page_id); break; case MALLARD_STATE_BLANK: case MALLARD_STATE_STOP: docuri = yelp_uri_get_document_uri (yelp_document_get_uri (document)); 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; default: g_assert_not_reached (); break; } g_mutex_unlock (&priv->mutex); return FALSE; } /******************************************************************************/ static void mallard_think (YelpMallardDocument *mallard) { YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (mallard); GError *error = NULL; gboolean editor_mode; gchar **path; gint path_i; GFile *gfile = NULL; GFileEnumerator *children = NULL; GFileInfo *pageinfo; editor_mode = yelp_settings_get_editor_mode (yelp_settings_get_default ()); path = yelp_uri_get_search_path (yelp_document_get_uri ((YelpDocument *) mallard)); if (!path || path[0] == NULL || !g_file_test (path[0], G_FILE_TEST_IS_DIR)) { /* This basically only happens when someone passes an actual directory manually, which will have a singleton search path. */ error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND, _("The directory ‘%s’ does not exist."), path && path[0] ? path[0] : "NULL"); yelp_document_error_pending ((YelpDocument *) mallard, error); g_error_free (error); goto done; } for (path_i = 0; path[path_i] != NULL; path_i++) { gfile = g_file_new_for_path (path[path_i]); children = g_file_enumerate_children (gfile, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL); while ((pageinfo = g_file_enumerator_next_file (children, NULL, NULL))) { MallardPageData *page_data; gchar *filename; GFile *pagefile; filename = g_file_info_get_attribute_as_string (pageinfo, G_FILE_ATTRIBUTE_STANDARD_NAME); if (!g_str_has_suffix (filename, ".page") && !(editor_mode && g_str_has_suffix (filename, ".page.stub"))) { g_free (filename); g_object_unref (pageinfo); continue; } page_data = g_new0 (MallardPageData, 1); page_data->mallard = mallard; pagefile = g_file_resolve_relative_path (gfile, filename); page_data->filename = g_file_get_path (pagefile); mallard_page_data_walk (page_data); if (page_data->page_id == NULL) { mallard_page_data_free (page_data); } else if (g_hash_table_lookup (priv->pages_hash, page_data->page_id) != NULL) { mallard_page_data_free (page_data); } else { g_mutex_lock (&priv->mutex); yelp_document_set_root_id ((YelpDocument *) mallard, page_data->page_id, "index"); yelp_document_set_page_id ((YelpDocument *) mallard, page_data->page_id, page_data->page_id); g_hash_table_insert (priv->pages_hash, page_data->page_id, page_data); yelp_document_set_page_title ((YelpDocument *) mallard, page_data->page_id, page_data->page_title); yelp_document_set_page_desc ((YelpDocument *) mallard, page_data->page_id, page_data->page_desc); yelp_document_set_page_keywords ((YelpDocument *) mallard, page_data->page_id, page_data->page_keywords); if (page_data->next_page != NULL) { yelp_document_set_next_id ((YelpDocument *) mallard, page_data->page_id, page_data->next_page); yelp_document_set_prev_id ((YelpDocument *) mallard, page_data->next_page, page_data->page_id); } yelp_document_signal ((YelpDocument *) mallard, page_data->page_id, YELP_DOCUMENT_SIGNAL_INFO, NULL); g_mutex_unlock (&priv->mutex); } g_object_unref (pagefile); g_free (filename); g_object_unref (pageinfo); } } g_strfreev (path); g_mutex_lock (&priv->mutex); priv->state = MALLARD_STATE_IDLE; while (priv->pending) { gchar *page_id = (gchar *) priv->pending->data; mallard_try_run (mallard, page_id); g_free (page_id); priv->pending = g_slist_delete_link (priv->pending, priv->pending); } g_mutex_unlock (&priv->mutex); done: g_object_unref (children); g_object_unref (gfile); priv->thread_running = FALSE; g_object_unref (mallard); } static void mallard_try_run (YelpMallardDocument *mallard, const gchar *page_id) { /* We expect to be in a locked mutex when this function is called. */ YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (mallard); MallardPageData *page_data = NULL; gchar *real_id = NULL; GError *error; debug_print (DB_FUNCTION, "entering\n"); debug_print (DB_ARG, " page_id=\"%s\"\n", page_id); if (page_id != NULL) real_id = yelp_document_get_page_id ((YelpDocument *) mallard, page_id); if (real_id != NULL) { page_data = g_hash_table_lookup (priv->pages_hash, real_id); g_free (real_id); } if (page_data == NULL) { gchar *docuri = yelp_uri_get_document_uri (yelp_document_get_uri ((YelpDocument *) mallard)); 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 ((YelpDocument *) mallard, page_id, YELP_DOCUMENT_SIGNAL_ERROR, error); g_error_free (error); return; } if (page_data->transform != NULL) { /* It's already running. Just let it be. */ return; } mallard_page_data_run (page_data); } /******************************************************************************/ /** MallardPageData ***********************************************************/ static void mallard_page_data_cancel (MallardPageData *page_data) { if (page_data->transform) { if (page_data->chunk_ready) { g_signal_handler_disconnect (page_data->transform, page_data->chunk_ready); page_data->chunk_ready = 0; } if (page_data->finished) { g_signal_handler_disconnect (page_data->transform, page_data->finished); page_data->finished = 0; } if (page_data->error) { g_signal_handler_disconnect (page_data->transform, page_data->error); page_data->error = 0; } yelp_transform_cancel (page_data->transform); g_object_unref (page_data->transform); page_data->transform = NULL; } } static void mallard_page_data_walk (MallardPageData *page_data) { YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (page_data->mallard); xmlParserCtxtPtr parserCtxt = NULL; xmlChar *id = NULL; if (page_data->cur == NULL) { parserCtxt = xmlNewParserCtxt (); page_data->xmldoc = xmlCtxtReadFile (parserCtxt, (const char *) page_data->filename, NULL, XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA | XML_PARSE_NOENT | XML_PARSE_NONET ); if (page_data->xmldoc == NULL) goto done; if (xmlXIncludeProcessFlags (page_data->xmldoc, XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA | XML_PARSE_NOENT | XML_PARSE_NONET ) < 0) goto done; page_data->cur = xmlDocGetRootElement (page_data->xmldoc); page_data->cache = xmlDocGetRootElement (priv->cache); page_data->xpath = xmlXPathNewContext (page_data->xmldoc); mallard_page_data_walk (page_data); } else { gboolean ispage; xmlNodePtr child, oldcur, oldcache, info; id = xmlGetProp (page_data->cur, BAD_CAST "id"); if (id == NULL) goto done; ispage = xml_node_is_ns_name (page_data->cur, MALLARD_NS, BAD_CAST "page"); if (ispage && g_hash_table_lookup (priv->pages_hash, id) != NULL) goto done; page_data->cache = xmlNewChild (page_data->cache, priv->mallard_ns, page_data->cur->name, NULL); if (ispage) { page_data->page_id = g_strdup ((gchar *) id); xmlSetProp (page_data->cache, BAD_CAST "id", id); yelp_document_set_page_id ((YelpDocument *) page_data->mallard, g_strrstr (page_data->filename, G_DIR_SEPARATOR_S), page_data->page_id); yelp_document_set_page_icon ((YelpDocument *) page_data->mallard, page_data->page_id, xml_node_get_icon (page_data->cur)); } else { gchar *newid = g_strdup_printf ("%s#%s", page_data->page_id, id); xmlSetProp (page_data->cache, BAD_CAST "id", BAD_CAST newid); g_free (newid); } info = xmlNewChild (page_data->cache, priv->mallard_ns, BAD_CAST "info", NULL); page_data->link_title = FALSE; page_data->sort_title = FALSE; for (child = page_data->cur->children; child; child = child->next) { if (child->type != XML_ELEMENT_NODE) continue; if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "info")) { mallard_page_data_info (page_data, child, info); } else if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "title")) { xmlNodePtr node; xmlNodePtr title_node = xmlNewChild (page_data->cache, priv->mallard_ns, BAD_CAST "title", NULL); for (node = child->children; node; node = node->next) { xmlAddChild (title_node, xmlCopyNode (node, 1)); } if (!page_data->link_title) { xmlNodePtr title_node2 = xmlNewChild (info, priv->mallard_ns, BAD_CAST "title", NULL); xmlSetProp (title_node2, BAD_CAST "type", BAD_CAST "link"); for (node = child->children; node; node = node->next) { xmlAddChild (title_node2, xmlCopyNode (node, 1)); } } if (!page_data->sort_title) { xmlNodePtr title_node2 = xmlNewChild (info, priv->mallard_ns, BAD_CAST "title", NULL); xmlSetProp (title_node2, BAD_CAST "type", BAD_CAST "sort"); for (node = child->children; node; node = node->next) { xmlAddChild (title_node2, xmlCopyNode (node, 1)); } } if (page_data->page_title == NULL) { xmlXPathObjectPtr obj; page_data->xpath->node = child; obj = xmlXPathCompiledEval (priv->normalize, page_data->xpath); page_data->page_title = g_strdup ((const gchar *) obj->stringval); xmlXPathFreeObject (obj); } } else if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "section")) { oldcur = page_data->cur; oldcache = page_data->cache; page_data->cur = child; mallard_page_data_walk (page_data); page_data->cur = oldcur; page_data->cache = oldcache; } } } done: if (id) xmlFree (id); if (parserCtxt) xmlFreeParserCtxt (parserCtxt); } static void mallard_page_data_info (MallardPageData *page_data, xmlNodePtr info_node, xmlNodePtr cache_node) { xmlNodePtr child; for (child = info_node->children; child; child = child->next) { if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "info")) { mallard_page_data_info (page_data, child, cache_node); } else if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "title")) { xmlNodePtr title_node; xmlChar *type, *role; title_node = xmlCopyNode (child, 1); xmlAddChild (cache_node, title_node); type = xmlGetProp (child, BAD_CAST "type"); if (type != NULL) { role = xmlGetProp (child, BAD_CAST "role"); if (xmlStrEqual (type, BAD_CAST "link") && role == NULL) page_data->link_title = TRUE; if (xmlStrEqual (type, BAD_CAST "sort")) page_data->sort_title = TRUE; if (xmlStrEqual (type, BAD_CAST "text")) { YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (page_data->mallard); xmlXPathObjectPtr obj; page_data->xpath->node = child; obj = xmlXPathCompiledEval (priv->normalize, page_data->xpath); g_free (page_data->page_title); page_data->page_title = g_strdup ((const gchar *) obj->stringval); xmlXPathFreeObject (obj); } if (role != NULL) xmlFree (role); xmlFree (type); } } else if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "desc")) { YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (page_data->mallard); xmlXPathObjectPtr obj; page_data->xpath->node = child; obj = xmlXPathCompiledEval (priv->normalize, page_data->xpath); g_free(page_data->page_desc); page_data->page_desc = g_strdup ((const gchar *) obj->stringval); xmlXPathFreeObject (obj); xmlAddChild (cache_node, xmlCopyNode (child, 1)); } else if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "keywords")) { /* FIXME: multiple keywords? same for desc/title */ YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (page_data->mallard); xmlXPathObjectPtr obj; page_data->xpath->node = child; obj = xmlXPathCompiledEval (priv->normalize, page_data->xpath); g_free(page_data->page_keywords); page_data->page_keywords = g_strdup ((const gchar *) obj->stringval); xmlXPathFreeObject (obj); xmlAddChild (cache_node, xmlCopyNode (child, 1)); } else if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "link")) { xmlChar *type, *next; xmlAddChild (cache_node, xmlCopyNode (child, 1)); type = xmlGetProp (child, BAD_CAST "type"); if (type != NULL) { if (xmlStrEqual (type, BAD_CAST "next")) { next = xmlGetProp (child, BAD_CAST "xref"); if (next != NULL) { if (page_data->next_page != NULL) g_free (page_data->next_page); page_data->next_page = g_strdup ((const gchar *) next); xmlFree (next); } } xmlFree (type); } } else if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "revision")) { xmlAddChild (cache_node, xmlCopyNode (child, 1)); } else { if (child->ns != NULL && !xmlStrEqual(child->ns->href, MALLARD_NS)) { xmlAddChild (cache_node, xmlCopyNode (child, 1)); } } } } static void mallard_page_data_run (MallardPageData *page_data) { YelpSettings *settings = yelp_settings_get_default (); YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (page_data->mallard); gchar **params = NULL; mallard_page_data_cancel (page_data); page_data->transform = yelp_transform_new (STYLESHEET); page_data->chunk_ready = g_signal_connect (page_data->transform, "chunk-ready", (GCallback) transform_chunk_ready, page_data); page_data->finished = g_signal_connect (page_data->transform, "finished", (GCallback) transform_finished, page_data); page_data->error = g_signal_connect (page_data->transform, "error", (GCallback) transform_error, page_data); if (g_str_has_suffix (page_data->filename, ".page.stub")) { gint end; params = yelp_settings_get_all_params (settings, 2, &end); params[end++] = g_strdup ("yelp.stub"); params[end++] = g_strdup ("true()"); } else params = yelp_settings_get_all_params (settings, 0, NULL); yelp_transform_start (page_data->transform, page_data->xmldoc, priv->cache, (const gchar * const *) params); g_strfreev (params); } static void mallard_page_data_free (MallardPageData *page_data) { mallard_page_data_cancel (page_data); g_free (page_data->page_id); g_free (page_data->filename); if (page_data->xmldoc) xmlFreeDoc (page_data->xmldoc); if (page_data->xpath) xmlXPathFreeContext (page_data->xpath); g_free (page_data->page_title); g_free (page_data->page_desc); g_free (page_data->page_keywords); g_free (page_data->next_page); g_free (page_data); } /******************************************************************************/ /** YelpTransform *************************************************************/ static void transform_chunk_ready (YelpTransform *transform, gchar *chunk_id, MallardPageData *page_data) { YelpMallardDocumentPrivate *priv; gchar *content; debug_print (DB_FUNCTION, "entering\n"); g_assert (page_data != NULL && page_data->mallard != NULL && YELP_IS_MALLARD_DOCUMENT (page_data->mallard)); g_assert (transform == page_data->transform); priv = yelp_mallard_document_get_instance_private (page_data->mallard); if (priv->state == MALLARD_STATE_STOP) { mallard_page_data_cancel (page_data); return; } content = yelp_transform_take_chunk (transform, chunk_id); yelp_document_give_contents (YELP_DOCUMENT (page_data->mallard), chunk_id, content, "application/xhtml+xml"); yelp_document_signal (YELP_DOCUMENT (page_data->mallard), chunk_id, YELP_DOCUMENT_SIGNAL_CONTENTS, NULL); } static void transform_finished (YelpTransform *transform, MallardPageData *page_data) { YelpMallardDocumentPrivate *priv; g_assert (page_data != NULL && page_data->mallard != NULL && YELP_IS_MALLARD_DOCUMENT (page_data->mallard)); g_assert (transform == page_data->transform); priv = yelp_mallard_document_get_instance_private (page_data->mallard); if (priv->state == MALLARD_STATE_STOP) { mallard_page_data_cancel (page_data); return; } mallard_page_data_cancel (page_data); if (page_data->xmldoc) xmlFreeDoc (page_data->xmldoc); page_data->xmldoc = NULL; } static void transform_error (YelpTransform *transform, MallardPageData *page_data) { YelpMallardDocumentPrivate *priv; GError *error; g_assert (page_data != NULL && page_data->mallard != NULL && YELP_IS_MALLARD_DOCUMENT (page_data->mallard)); g_assert (transform == page_data->transform); priv = yelp_mallard_document_get_instance_private (page_data->mallard); if (priv->state == MALLARD_STATE_STOP) { mallard_page_data_cancel (page_data); return; } error = yelp_transform_get_error (transform); yelp_document_error_pending ((YelpDocument *) (page_data->mallard), error); g_error_free (error); mallard_page_data_cancel (page_data); } static const char * xml_node_get_icon (xmlNodePtr node) { xmlChar *style; gchar **styles; const gchar *icon = "yelp-page-symbolic"; style = xmlGetProp (node, BAD_CAST "style"); if (style) { gint i; styles = g_strsplit ((const gchar *)style, " ", -1); for (i = 0; styles[i] != NULL; i++) { if (g_str_equal (styles[i], "video")) { icon = "yelp-page-video-symbolic"; break; } else if (g_str_equal (styles[i], "task")) { icon = "yelp-page-task-symbolic"; break; } else if (g_str_equal (styles[i], "tip")) { icon = "yelp-page-tip-symbolic"; break; } else if (g_str_equal (styles[i], "problem")) { icon = "yelp-page-problem-symbolic"; break; } else if (g_str_equal (styles[i], "ui")) { icon = "yelp-page-ui-symbolic"; break; } } g_strfreev (styles); xmlFree (style); } return icon; } static gboolean xml_node_is_ns_name (xmlNodePtr node, const xmlChar *ns, const xmlChar *name) { if (node->ns == NULL) return (ns == NULL); else if (ns != NULL && node->ns->href != NULL) return (xmlStrEqual (ns, node->ns->href) && xmlStrEqual (name, node->name)); return FALSE; } /******************************************************************************/ typedef struct { YelpMallardDocument *mallard; xmlDocPtr doc; xmlNodePtr cur; GString *str; gboolean is_inline; gboolean in_info; } MallardIndexData; static void mallard_index_node (MallardIndexData *index) { xmlNodePtr orig, child; gboolean was_inline, was_info; orig = index->cur; was_inline = index->is_inline; was_info = index->in_info; for (child = index->cur->children; child; child = child->next) { if (index->is_inline) { if ((xml_node_is_ns_name (child->parent, MALLARD_NS, BAD_CAST "guiseq") || xml_node_is_ns_name (child->parent, MALLARD_NS, BAD_CAST "keyseq")) && child->prev != NULL) { g_string_append_c (index->str, ' '); } if (child->type == XML_TEXT_NODE) { g_string_append (index->str, (const gchar *) child->content); continue; } } if (child->type != XML_ELEMENT_NODE || xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "comment")) continue; if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "info")) { index->in_info = TRUE; } else if (xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "p") || xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "code") || xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "screen") || xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "title") || xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "desc") || xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "keywords") || xml_node_is_ns_name (child, MALLARD_NS, BAD_CAST "cite")) { index->is_inline = TRUE; } else if (index->in_info && !index->is_inline) { continue; } index->cur = child; mallard_index_node (index); if (index->is_inline && !was_inline) { g_string_append_c (index->str, '\n'); } index->cur = orig; index->is_inline = was_inline; index->in_info = was_info; } } static gboolean mallard_index_done (YelpMallardDocument *mallard) { g_object_set (mallard, "indexed", TRUE, NULL); return FALSE; } static void mallard_index_threaded (YelpMallardDocument *mallard) { gchar **path; gint path_i; GHashTable *ids; gchar *doc_uri; YelpUri *document_uri; YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (mallard); document_uri = yelp_document_get_uri (YELP_DOCUMENT (mallard)); doc_uri = yelp_uri_get_document_uri (document_uri); ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); path = yelp_uri_get_search_path (document_uri); for (path_i = 0; path[path_i] != NULL; path_i++) { GFile *gfile; GFileEnumerator *children; GFileInfo *pageinfo; gfile = g_file_new_for_path (path[path_i]); children = g_file_enumerate_children (gfile, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL); while ((pageinfo = g_file_enumerator_next_file (children, NULL, NULL))) { gchar *filename; GFile *pagefile; xmlParserCtxtPtr parserCtxt = NULL; xmlXPathContextPtr xpath = NULL; xmlXPathObjectPtr obj; MallardIndexData *index = NULL; xmlChar *id = NULL; YelpUri *uri; gchar *title, *desc, *fulltext, *tmp, *full_uri; filename = g_file_info_get_attribute_as_string (pageinfo, G_FILE_ATTRIBUTE_STANDARD_NAME); if (!g_str_has_suffix (filename, ".page")) { g_free (filename); g_object_unref (pageinfo); continue; } pagefile = g_file_resolve_relative_path (gfile, filename); g_free (filename); filename = g_file_get_path (pagefile); index = g_new0 (MallardIndexData, 1); index->mallard = mallard; parserCtxt = xmlNewParserCtxt (); index->doc = xmlCtxtReadFile (parserCtxt, filename, NULL, XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA | XML_PARSE_NOENT | XML_PARSE_NONET ); if (index->doc == NULL) goto done; if (xmlXIncludeProcessFlags (index->doc, XML_PARSE_DTDLOAD | XML_PARSE_NOCDATA | XML_PARSE_NOENT | XML_PARSE_NONET ) < 0) goto done; index->cur = xmlDocGetRootElement (index->doc); id = xmlGetProp (index->cur, BAD_CAST "id"); if (id == NULL) goto done; if (g_hash_table_lookup (ids, id)) goto done; /* We never use the value. Just need something non-NULL. */ g_hash_table_insert (ids, g_strdup ((const gchar *) id), id); xpath = xmlXPathNewContext (index->doc); xmlXPathRegisterNs (xpath, BAD_CAST "mal", MALLARD_NS); obj = xmlXPathEvalExpression (BAD_CAST "normalize-space((/mal:page/mal:info/mal:title[@type='text'] |" "/mal:page/mal:title)[1])", xpath); title = g_strdup ((const gchar *) obj->stringval); xmlXPathFreeObject (obj); obj = xmlXPathEvalExpression (BAD_CAST "normalize-space(/mal:page/mal:info/mal:desc[1])", xpath); desc = g_strdup ((const gchar *) obj->stringval); xmlXPathFreeObject (obj); index->str = g_string_new ((const gchar *) desc); g_string_append_c (index->str, ' '); mallard_index_node (index); tmp = g_strconcat ("xref:", id, NULL); uri = yelp_uri_new_relative (document_uri, tmp); yelp_uri_resolve_sync (uri); full_uri = yelp_uri_get_canonical_uri (uri); g_free (tmp); g_object_unref (uri); fulltext = g_string_free (index->str, FALSE); yelp_storage_update (yelp_storage_get_default (), doc_uri, full_uri, title, desc, xml_node_get_icon (xmlDocGetRootElement (index->doc)), fulltext); if (g_str_equal (id, "index")) yelp_storage_set_root_title (yelp_storage_get_default (), doc_uri, title); g_free (full_uri); g_free (title); g_free (desc); g_free (fulltext); done: if (id) xmlFree (id); if (xpath != NULL) xmlXPathFreeContext (xpath); if (index->doc != NULL) xmlFreeDoc (index->doc); g_free (index); if (parserCtxt != NULL) xmlFreeParserCtxt (parserCtxt); g_object_unref (pagefile); g_free (filename); g_object_unref (pageinfo); } } g_strfreev (path); g_hash_table_destroy (ids); priv->index_running = FALSE; g_free (doc_uri); g_object_unref (mallard); g_idle_add ((GSourceFunc) mallard_index_done, mallard); } static void mallard_index (YelpDocument *document) { YelpMallardDocumentPrivate *priv; gboolean done; g_object_get (document, "indexed", &done, NULL); if (done) return; priv = yelp_mallard_document_get_instance_private (YELP_MALLARD_DOCUMENT (document)); g_object_ref (document); priv->index = g_thread_new ("mallard-index", (GThreadFunc)(GCallback) mallard_index_threaded, document); priv->index_running = TRUE; } static void mallard_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, YelpMallardDocument *mallard) { gchar **ids; gint i; xmlNodePtr cur; YelpMallardDocumentPrivate *priv = yelp_mallard_document_get_instance_private (mallard); /* Exiting the thinking thread would require a fair amount of retooling. For now, we'll just fail to auto-reload if that's still happening. */ if (priv->thread_running) return; g_mutex_lock (&priv->mutex); g_hash_table_remove_all (priv->pages_hash); g_object_set (mallard, "indexed", FALSE, NULL); ids = yelp_document_get_requests (YELP_DOCUMENT (mallard)); for (i = 0; ids[i]; i++) { priv->pending = g_slist_prepend (priv->pending, ids[i]); } g_free (ids); yelp_document_clear_contents (YELP_DOCUMENT (mallard)); xmlFreeDoc (priv->cache); priv->cache = xmlNewDoc (BAD_CAST "1.0"); priv->cache_ns = xmlNewNs (NULL, CACHE_NS, BAD_CAST "cache"); cur = xmlNewDocNode (priv->cache, priv->cache_ns, BAD_CAST "cache", NULL); xmlDocSetRootElement (priv->cache, cur); priv->cache_ns->next = priv->mallard_ns; priv->mallard_ns->next = cur->nsDef; cur->nsDef = priv->cache_ns; priv->state = MALLARD_STATE_THINKING; priv->thread_running = TRUE; g_object_ref (mallard); priv->thread = g_thread_new ("mallard-reload", (GThreadFunc)(GCallback) mallard_think, mallard); g_mutex_unlock (&priv->mutex); }