yelp-location-entry: Icon pixbufs for history drop-down
[yelp.git] / libyelp / yelp-uri.c
blob14b80e140120b6f15016bb8e7ff145f5c5faaed6
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 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>
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
27 #include <string.h>
28 #include <stdio.h>
30 #include <glib.h>
31 #include <gio/gio.h>
33 #include "yelp-uri.h"
34 #include "yelp-debug.h"
35 #include "yelp-settings.h"
37 static void yelp_uri_class_init (YelpUriClass *klass);
38 static void yelp_uri_init (YelpUri *uri);
39 static void yelp_uri_dispose (GObject *object);
40 static void yelp_uri_finalize (GObject *object);
42 static void resolve_start (YelpUri *uri);
43 static void resolve_sync (YelpUri *uri);
44 static void resolve_async (YelpUri *uri);
45 static gboolean resolve_final (YelpUri *uri);
47 static void resolve_file_uri (YelpUri *uri);
48 static void resolve_file_path (YelpUri *uri);
49 static void resolve_data_dirs (YelpUri *uri,
50 const gchar *subdir,
51 const gchar *docid,
52 const gchar *pageid,
53 gboolean langfirst);
54 static void resolve_ghelp_uri (YelpUri *uri);
55 static void resolve_help_uri (YelpUri *uri);
56 static void resolve_help_list_uri (YelpUri *uri);
57 static void resolve_man_uri (YelpUri *uri);
58 static void resolve_info_uri (YelpUri *uri);
59 static void resolve_xref_uri (YelpUri *uri);
60 static void resolve_page_and_frag (YelpUri *uri,
61 const gchar *arg);
62 static void resolve_gfile (YelpUri *uri,
63 const gchar *hash);
65 static gboolean is_man_path (const gchar *uri,
66 const gchar *encoding);
68 G_DEFINE_TYPE (YelpUri, yelp_uri, G_TYPE_OBJECT);
69 #define GET_PRIV(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_URI, YelpUriPrivate))
71 typedef struct _YelpUriPrivate YelpUriPrivate;
72 struct _YelpUriPrivate {
73 GThread *resolver;
75 YelpUriDocumentType doctype;
76 YelpUriDocumentType tmptype;
78 gchar *docuri;
79 gchar *fulluri;
80 GFile *gfile;
82 gchar **search_path;
83 gchar *page_id;
84 gchar *frag_id;
86 /* Unresolved */
87 YelpUri *res_base;
88 gchar *res_arg;
91 enum {
92 RESOLVED,
93 LAST_SIGNAL
95 static guint uri_signals[LAST_SIGNAL] = {0,};
97 /******************************************************************************/
99 static const gchar *mancats[] = {
100 "0p",
101 "1", "1p", "1g", "1t", "1x", "1ssl", "1m",
102 "2",
103 "3", "3o", "3t", "3p", "3blt", "3nas", "3form", "3menu", "3tiff", "3ssl", "3readline",
104 "3ncurses", "3curses", "3f", "3pm", "3perl", "3qt", "3x", "3X11",
105 "4", "4x",
106 "5", "5snmp", "5x", "5ssl",
107 "6", "6x",
108 "7", "7gcc", "7x", "7ssl",
109 "8", "8l", "9", "0p",
110 NULL
113 static const gchar *infosuffix[] = {
114 ".info",
115 ".info.gz", ".info.bz2", ".info.lzma",
116 ".gz", ".bz2", ".lzma",
117 NULL
120 static const gchar default_info_path[] =
121 "/usr/info:/usr/share/info:/usr/local/info:/usr/local/share/info";
123 /******************************************************************************/
125 static void
126 yelp_uri_class_init (YelpUriClass *klass)
128 GObjectClass *object_class = G_OBJECT_CLASS (klass);
130 object_class->dispose = yelp_uri_dispose;
131 object_class->finalize = yelp_uri_finalize;
133 uri_signals[RESOLVED] =
134 g_signal_new ("resolved",
135 G_OBJECT_CLASS_TYPE (klass),
136 G_SIGNAL_RUN_LAST,
137 0, NULL, NULL,
138 g_cclosure_marshal_VOID__VOID,
139 G_TYPE_NONE, 0);
141 g_type_class_add_private (klass, sizeof (YelpUriPrivate));
144 static void
145 yelp_uri_init (YelpUri *uri)
147 return;
150 static void
151 yelp_uri_dispose (GObject *object)
153 YelpUriPrivate *priv = GET_PRIV (object);
155 if (priv->gfile) {
156 g_object_unref (priv->gfile);
157 priv->gfile = NULL;
160 if (priv->res_base) {
161 g_object_unref (priv->res_base);
162 priv->res_base = NULL;
165 G_OBJECT_CLASS (yelp_uri_parent_class)->dispose (object);
168 static void
169 yelp_uri_finalize (GObject *object)
171 YelpUriPrivate *priv = GET_PRIV (object);
173 g_free (priv->docuri);
174 g_free (priv->fulluri);
175 g_strfreev (priv->search_path);
176 g_free (priv->page_id);
177 g_free (priv->frag_id);
178 g_free (priv->res_arg);
180 G_OBJECT_CLASS (yelp_uri_parent_class)->finalize (object);
183 /******************************************************************************/
185 YelpUri *
186 yelp_uri_new (const gchar *arg)
188 return yelp_uri_new_relative (NULL, arg);
191 YelpUri *
192 yelp_uri_new_relative (YelpUri *base, const gchar *arg)
194 YelpUri *uri;
195 YelpUriPrivate *priv;
197 uri = (YelpUri *) g_object_new (YELP_TYPE_URI, NULL);
199 priv = GET_PRIV (uri);
200 priv->doctype = YELP_URI_DOCUMENT_TYPE_UNRESOLVED;
201 if (base)
202 priv->res_base = g_object_ref (base);
203 priv->res_arg = g_strdup (arg);
205 return uri;
208 YelpUri *
209 yelp_uri_new_search (YelpUri *base,
210 const gchar *text)
212 YelpUri *uri;
213 YelpUriPrivate *priv;
214 gchar *tmp;
216 uri = (YelpUri *) g_object_new (YELP_TYPE_URI, NULL);
218 priv = GET_PRIV (uri);
219 priv->doctype = YELP_URI_DOCUMENT_TYPE_UNRESOLVED;
220 if (base)
221 priv->res_base = g_object_ref (base);
222 tmp = g_uri_escape_string (text, NULL, FALSE);
223 priv->res_arg = g_strconcat("xref:search=", tmp, NULL);
224 g_free (tmp);
226 return uri;
229 /******************************************************************************/
231 void
232 yelp_uri_resolve (YelpUri *uri)
234 YelpUriPrivate *priv = GET_PRIV (uri);
236 if (priv->res_base && !yelp_uri_is_resolved (priv->res_base)) {
237 g_signal_connect_swapped (priv->res_base, "resolved",
238 G_CALLBACK (resolve_start),
239 uri);
240 yelp_uri_resolve (priv->res_base);
242 else {
243 resolve_start (uri);
247 void
248 yelp_uri_resolve_sync (YelpUri *uri)
250 YelpUriPrivate *priv = GET_PRIV (uri);
252 if (priv->doctype != YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
253 return;
255 if (priv->res_base)
256 yelp_uri_resolve_sync (priv->res_base);
258 g_object_ref (uri);
259 resolve_sync (uri);
260 resolve_final (uri);
263 /* We want code to be able to do something like this:
265 * if (yelp_uri_get_document_type (uri) != YELP_URI_DOCUMENT_TYPE_UNRESOLVED) {
266 * g_signal_connect (uri, "resolve", callback, data);
267 * yelp_uri_resolve (uri);
270 * Resolving happens in a separate thread, though, so if that thread can change
271 * the document type, we have a race condition. So here's the rules we play by:
273 * 1) None of the getters except the document type getter can return real data
274 * while the URI is unresolved. They all do a resolved check first, and
275 * return NULL if the URI is not resolved.
277 * 2) The threaded resolver functions can modify anything but the document
278 * type. They are the only things that are allowed to modify that data.
280 * 3) The resolver thread is not allowed to modify the document type. When
281 * it's done, it queues an async function to set the document type and
282 * emit "resolved" in the main thread.
284 * 4) Once a URI is resolved, it is immutable.
286 static void
287 resolve_start (YelpUri *uri)
289 YelpUriPrivate *priv = GET_PRIV (uri);
291 if (priv->resolver == NULL) {
292 g_object_ref (uri);
293 priv->resolver = g_thread_create ((GThreadFunc) resolve_async,
294 uri, FALSE, NULL);
298 static void
299 resolve_sync (YelpUri *uri)
301 gchar *tmp;
302 YelpUriPrivate *priv = GET_PRIV (uri);
304 if (g_str_has_prefix (priv->res_arg, "ghelp:")
305 || g_str_has_prefix (priv->res_arg, "gnome-help:")) {
306 resolve_ghelp_uri (uri);
308 else if (g_str_has_prefix (priv->res_arg, "help:")) {
309 resolve_help_uri (uri);
311 else if (g_str_has_prefix (priv->res_arg, "help-list:")) {
312 resolve_help_list_uri (uri);
314 else if (g_str_has_prefix (priv->res_arg, "file:")) {
315 resolve_file_uri (uri);
317 else if (g_str_has_prefix (priv->res_arg, "man:")) {
318 resolve_man_uri (uri);
320 else if (g_str_has_prefix (priv->res_arg, "info:")) {
321 resolve_info_uri (uri);
323 else if (g_str_has_prefix (priv->res_arg, "xref:")) {
324 YelpUriPrivate *base_priv;
325 if (priv->res_base == NULL) {
326 priv->tmptype = YELP_URI_DOCUMENT_TYPE_ERROR;
327 return;
329 base_priv = GET_PRIV (priv->res_base);
330 switch (base_priv->doctype) {
331 case YELP_URI_DOCUMENT_TYPE_UNRESOLVED:
332 break;
333 case YELP_URI_DOCUMENT_TYPE_DOCBOOK:
334 case YELP_URI_DOCUMENT_TYPE_MALLARD:
335 case YELP_URI_DOCUMENT_TYPE_INFO:
336 resolve_xref_uri (uri);
337 break;
338 case YELP_URI_DOCUMENT_TYPE_MAN: {
339 gchar *tmp = g_strconcat ("man:", priv->res_arg + 5, NULL);
340 g_free (priv->res_arg);
341 priv->res_arg = tmp;
342 resolve_man_uri (uri);
343 break;
345 case YELP_URI_DOCUMENT_TYPE_TEXT:
346 case YELP_URI_DOCUMENT_TYPE_HTML:
347 case YELP_URI_DOCUMENT_TYPE_XHTML:
348 resolve_file_path (uri);
349 break;
350 case YELP_URI_DOCUMENT_TYPE_HELP_LIST:
351 /* FIXME: what do we do? */
352 break;
353 case YELP_URI_DOCUMENT_TYPE_NOT_FOUND:
354 case YELP_URI_DOCUMENT_TYPE_EXTERNAL:
355 case YELP_URI_DOCUMENT_TYPE_ERROR:
356 break;
359 else if (strchr (priv->res_arg, ':')) {
360 priv->tmptype = YELP_URI_DOCUMENT_TYPE_EXTERNAL;
362 else {
363 resolve_file_path (uri);
366 /* We _always_ want to have a non-null fulluri, so check for it
367 * having been set here and, if we can't think of something
368 * better, set it to res_arg. */
369 if (!priv->fulluri) {
370 priv->fulluri = g_strdup (priv->res_arg);
374 static void
375 resolve_async (YelpUri *uri)
377 resolve_sync (uri);
378 g_idle_add ((GSourceFunc) resolve_final, uri);
381 static gboolean
382 resolve_final (YelpUri *uri)
384 YelpUriPrivate *priv = GET_PRIV (uri);
386 priv->resolver = NULL;
388 if (priv->tmptype != YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
389 priv->doctype = priv->tmptype;
390 else
391 priv->doctype = YELP_URI_DOCUMENT_TYPE_ERROR;
393 if (priv->res_base) {
394 g_object_unref (priv->res_base);
395 priv->res_base = NULL;
398 if (priv->res_arg) {
399 g_free (priv->res_arg);
400 priv->res_arg = NULL;
403 g_signal_emit (uri, uri_signals[RESOLVED], 0);
404 g_object_unref (uri);
405 return FALSE;
408 /******************************************************************************/
410 gboolean
411 yelp_uri_is_resolved (YelpUri *uri)
413 YelpUriPrivate *priv = GET_PRIV (uri);
414 return priv->doctype != YELP_URI_DOCUMENT_TYPE_UNRESOLVED;
417 YelpUriDocumentType
418 yelp_uri_get_document_type (YelpUri *uri)
420 YelpUriPrivate *priv = GET_PRIV (uri);
421 return priv->doctype;
424 gchar *
425 yelp_uri_get_document_uri (YelpUri *uri)
427 YelpUriPrivate *priv = GET_PRIV (uri);
428 if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
429 return NULL;
431 /* There's some client code where it makes sense to want a
432 * document uri, whether or not it conforms to a scheme we really
433 * understand. For example, we might want to look up whether the
434 * given page is currently being visited. */
435 if ((!priv->docuri) && priv->fulluri) {
436 return g_strdup (priv->fulluri);
439 return g_strdup (priv->docuri);
442 gchar *
443 yelp_uri_get_canonical_uri (YelpUri *uri)
445 YelpUriPrivate *priv = GET_PRIV (uri);
446 if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
447 return NULL;
448 return g_strdup (priv->fulluri);
451 GFile *
452 yelp_uri_get_file (YelpUri *uri)
454 YelpUriPrivate *priv = GET_PRIV (uri);
455 if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
456 return NULL;
457 return priv->gfile ? g_object_ref (priv->gfile) : NULL;
460 gchar **
461 yelp_uri_get_search_path (YelpUri *uri)
463 YelpUriPrivate *priv = GET_PRIV (uri);
464 if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
465 return NULL;
466 return g_strdupv (priv->search_path);
469 gchar *
470 yelp_uri_get_page_id (YelpUri *uri)
472 YelpUriPrivate *priv = GET_PRIV (uri);
473 if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
474 return NULL;
475 return g_strdup (priv->page_id);
478 gchar *
479 yelp_uri_get_frag_id (YelpUri *uri)
481 YelpUriPrivate *priv = GET_PRIV (uri);
482 if (priv->doctype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
483 return NULL;
484 return g_strdup (priv->frag_id);
487 /******************************************************************************/
489 gchar *
490 yelp_uri_locate_file_uri (YelpUri *uri,
491 const gchar *filename)
493 YelpUriPrivate *priv = GET_PRIV (uri);
494 GFile *gfile;
495 gchar *fullpath;
496 gchar *returi = NULL;
497 gint i;
498 for (i = 0; priv->search_path[i] != NULL; i++) {
499 fullpath = g_strconcat (priv->search_path[i],
500 G_DIR_SEPARATOR_S,
501 filename,
502 NULL);
503 if (g_file_test (fullpath, G_FILE_TEST_EXISTS)) {
504 gfile = g_file_new_for_path (fullpath);
505 returi = g_file_get_uri (gfile);
506 g_object_unref (gfile);
508 g_free (fullpath);
509 if (returi)
510 break;
512 return returi;
515 /******************************************************************************/
517 static void
518 resolve_file_uri (YelpUri *uri)
520 YelpUriPrivate *priv = GET_PRIV (uri);
521 gchar *uristr;
522 const gchar *hash = strchr (priv->res_arg, '#');
524 if (hash) {
525 uristr = g_strndup (priv->res_arg, hash - priv->res_arg);
526 hash++;
528 else
529 uristr = priv->res_arg;
531 priv->gfile = g_file_new_for_uri (uristr);
533 resolve_gfile (uri, hash);
536 static void
537 resolve_file_path (YelpUri *uri)
539 YelpUriPrivate *base_priv = NULL;
540 YelpUriPrivate *priv = GET_PRIV (uri);
541 gchar *path;
542 const gchar *hash = strchr (priv->res_arg, '#');
544 /* Treat xref: URIs like relative file paths */
545 if (g_str_has_prefix (priv->res_arg, "xref:")) {
546 gchar *tmp = g_strdup (priv->res_arg + 5);
547 g_free (priv->res_arg);
548 priv->res_arg = tmp;
551 if (priv->res_base)
552 base_priv = GET_PRIV (priv->res_base);
554 if (hash) {
555 path = g_strndup (priv->res_arg, hash - priv->res_arg);
556 hash++;
558 else
559 path = priv->res_arg;
561 if (priv->res_arg[0] == '/') {
562 priv->gfile = g_file_new_for_path (path);
564 else if (base_priv && base_priv->gfile) {
565 GFileInfo *info;
566 info = g_file_query_info (base_priv->gfile,
567 G_FILE_ATTRIBUTE_STANDARD_TYPE,
568 G_FILE_QUERY_INFO_NONE,
569 NULL, NULL);
570 if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR) {
571 GFile *parent = g_file_get_parent (base_priv->gfile);
572 priv->gfile = g_file_resolve_relative_path (parent, path);
573 g_object_unref (parent);
575 else {
576 priv->gfile = g_file_resolve_relative_path (base_priv->gfile, path);
579 else {
580 gchar *cur;
581 GFile *curfile;
582 cur = g_get_current_dir ();
583 curfile = g_file_new_for_path (cur);
584 priv->gfile = g_file_resolve_relative_path (curfile, path);
585 g_object_unref (curfile);
586 g_free (cur);
589 resolve_gfile (uri, hash);
592 static void
593 resolve_data_dirs (YelpUri *ret,
594 const gchar *subdir,
595 const gchar *docid,
596 const gchar *pageid,
597 gboolean langfirst)
599 const gchar * const *sdatadirs = g_get_system_data_dirs ();
600 const gchar * const *langs = g_get_language_names ();
601 /* The strings are still owned by GLib; we just own the array. */
602 gchar **datadirs;
603 YelpUriPrivate *priv = GET_PRIV (ret);
604 gchar *filename = NULL;
605 gchar **searchpath = NULL;
606 gint searchi, searchmax;
607 gint datadir_i, lang_i;
609 datadirs = g_new0 (gchar *, g_strv_length ((gchar **) sdatadirs) + 2);
610 datadirs[0] = (gchar *) g_get_user_data_dir ();
611 for (datadir_i = 0; sdatadirs[datadir_i]; datadir_i++)
612 datadirs[datadir_i + 1] = (gchar *) sdatadirs[datadir_i];
614 searchi = 0;
615 searchmax = 10;
616 searchpath = g_new0 (gchar *, 10);
618 for (datadir_i = 0; datadirs[datadir_i]; datadir_i++) {
619 for (lang_i = 0; langs[lang_i]; lang_i++) {
620 gchar *helpdir = g_build_filename (datadirs[datadir_i],
621 subdir,
622 langfirst ? langs[lang_i] : docid,
623 langfirst ? docid : langs[lang_i],
624 NULL);
625 if (!g_file_test (helpdir, G_FILE_TEST_IS_DIR)) {
626 g_free (helpdir);
627 continue;
630 if (searchi + 1 >= searchmax) {
631 searchmax += 5;
632 searchpath = g_renew (gchar *, searchpath, searchmax);
634 searchpath[searchi] = helpdir;
635 searchpath[++searchi] = NULL;
637 if (priv->tmptype != YELP_URI_DOCUMENT_TYPE_UNRESOLVED)
638 /* We've already found it. We're just adding to the search path now. */
639 continue;
641 filename = g_strdup_printf ("%s/index.page", helpdir);
642 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
643 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MALLARD;
644 g_free (filename);
645 filename = g_strdup (helpdir);
646 continue;
648 g_free (filename);
650 if (langfirst) {
651 filename = g_strdup_printf ("%s/index.docbook", helpdir);
652 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
653 priv->tmptype = YELP_URI_DOCUMENT_TYPE_DOCBOOK;
654 continue;
656 g_free (filename);
658 else {
659 filename = g_strdup_printf ("%s/%s.xml", helpdir, pageid);
660 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
661 priv->tmptype = YELP_URI_DOCUMENT_TYPE_DOCBOOK;
662 continue;
664 g_free (filename);
667 filename = g_strdup_printf ("%s/%s.html", helpdir, pageid);
668 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
669 priv->tmptype = YELP_URI_DOCUMENT_TYPE_HTML;
670 continue;
672 g_free (filename);
674 filename = g_strdup_printf ("%s/%s.xhtml", helpdir, pageid);
675 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
676 priv->tmptype = YELP_URI_DOCUMENT_TYPE_XHTML;
677 continue;
679 g_free (filename);
680 } /* end for langs */
681 } /* end for datadirs */
683 g_free (datadirs);
684 if (priv->tmptype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED) {
685 g_strfreev (searchpath);
686 priv->tmptype = YELP_URI_DOCUMENT_TYPE_NOT_FOUND;
688 else {
689 priv->gfile = g_file_new_for_path (filename);
690 priv->search_path = searchpath;
694 static void
695 resolve_ghelp_uri (YelpUri *uri)
697 /* ghelp:/path/to/file
698 * ghelp:document[/file][?page][#frag]
700 YelpUriPrivate *priv = GET_PRIV (uri);
701 gchar *document, *slash, *query, *hash;
702 gchar *colon, *c; /* do not free */
704 colon = strchr (priv->res_arg, ':');
705 if (!colon) {
706 priv->tmptype = YELP_URI_DOCUMENT_TYPE_ERROR;
707 return;
710 slash = query = hash = NULL;
711 for (c = colon; *c != '\0'; c++) {
712 if (*c == '#' && hash == NULL)
713 hash = c;
714 else if (*c == '?' && query == NULL && hash == NULL)
715 query = c;
716 else if (*c == '/' && slash == NULL && query == NULL && hash == NULL)
717 slash = c;
720 if (slash || query || hash)
721 document = g_strndup (colon + 1,
722 (slash ? slash : (query ? query : hash)) - colon - 1);
723 else
724 document = g_strdup (colon + 1);
726 if (slash && (query || hash))
727 slash = g_strndup (slash + 1,
728 (query ? query : hash) - slash - 1);
729 else if (slash)
730 slash = g_strdup (slash + 1);
732 if (query && hash)
733 query = g_strndup (query + 1,
734 hash - query - 1);
735 else if (query)
736 query = g_strdup (query + 1);
738 if (hash)
739 hash = g_strdup (hash + 1);
741 if (*(colon + 1) == '/') {
742 gchar *newuri;
743 newuri = g_strdup_printf ("file:/%s", slash);
744 g_free (priv->res_arg);
745 priv->res_arg = newuri;
746 resolve_file_uri (uri);
748 else {
749 resolve_data_dirs (uri, "gnome/help", document, slash ? slash : document, FALSE);
752 if (query && hash) {
753 priv->page_id = query;
754 priv->frag_id = hash;
756 else if (query) {
757 priv->page_id = query;
758 if (priv->tmptype != YELP_URI_DOCUMENT_TYPE_MALLARD)
759 priv->frag_id = g_strdup (query);
761 else if (hash) {
762 priv->page_id = hash;
763 priv->frag_id = g_strdup (hash);
766 if (priv->frag_id && g_str_has_prefix (priv->frag_id, "search=")) {
767 g_free (priv->frag_id);
768 priv->frag_id = NULL;
771 priv->docuri = g_strconcat ("ghelp:", document,
772 slash ? "/" : NULL,
773 slash, NULL);
775 priv->fulluri = g_strconcat (priv->docuri,
776 priv->page_id ? "?" : "",
777 priv->page_id ? priv->page_id : "",
778 priv->frag_id ? "#" : "",
779 priv->frag_id ? priv->frag_id : "",
780 NULL);
782 g_free (document);
783 g_free (slash);
784 return;
787 static void
788 resolve_help_uri (YelpUri *uri)
790 /* help:document[/page][?query][#frag]
792 YelpUriPrivate *priv = GET_PRIV (uri);
793 gchar *document, *slash, *query, *hash;
794 gchar *colon, *c; /* do not free */
796 colon = strchr (priv->res_arg, ':');
797 if (!colon) {
798 priv->tmptype = YELP_URI_DOCUMENT_TYPE_ERROR;
799 return;
802 slash = query = hash = NULL;
803 for (c = colon; *c != '\0'; c++) {
804 if (*c == '#' && hash == NULL)
805 hash = c;
806 else if (*c == '?' && query == NULL && hash == NULL)
807 query = c;
808 else if (*c == '/' && slash == NULL && query == NULL && hash == NULL)
809 slash = c;
812 if (slash || query || hash)
813 document = g_strndup (colon + 1,
814 (slash ? slash : (query ? query : hash)) - colon - 1);
815 else
816 document = g_strdup (colon + 1);
818 if (slash && (query || hash))
819 slash = g_strndup (slash + 1,
820 (query ? query : hash) - slash - 1);
821 else if (slash)
822 slash = g_strdup (slash + 1);
824 if (query && hash)
825 query = g_strndup (query + 1,
826 hash - query - 1);
827 else if (query)
828 query = g_strdup (query + 1);
830 if (hash)
831 hash = g_strdup (hash + 1);
833 priv->page_id = (slash ? slash : g_strdup ("index"));
834 resolve_data_dirs (uri, "help", document, priv->page_id, TRUE);
836 if (hash)
837 priv->frag_id = hash;
838 if (priv->frag_id && g_str_has_prefix (priv->frag_id, "search=")) {
839 g_free (priv->frag_id);
840 priv->frag_id = NULL;
843 priv->docuri = g_strconcat ("help:", document, NULL);
845 priv->fulluri = g_strconcat (priv->docuri,
846 priv->page_id ? "/" : "",
847 priv->page_id ? priv->page_id : "",
848 query ? "?" : "",
849 query ? query : "",
850 priv->frag_id ? "#" : "",
851 priv->frag_id ? priv->frag_id : "",
852 NULL);
854 g_free (query);
855 g_free (document);
856 return;
859 static void
860 resolve_help_list_uri (YelpUri *uri)
862 YelpUriPrivate *priv = GET_PRIV (uri);
863 priv->docuri = g_strdup ("help-list:");
864 priv->fulluri = g_strdup (priv->res_arg);
865 priv->page_id = g_strdup ("index");
866 priv->tmptype = YELP_URI_DOCUMENT_TYPE_HELP_LIST;
870 Resolve a manual file's path using 'man -w'. section may be NULL,
871 otherwise should be the section of the manual (ie should have dealt
872 with empty strings before calling this!) Returns NULL if the file
873 can't be found.
875 static gchar*
876 find_man_path (gchar* name, gchar* section)
878 gchar* argv[] = { "man", "-w", NULL, NULL, NULL };
879 gchar *stdout = NULL;
880 gint status;
881 gchar **lines;
882 GError *error = NULL;
884 /* Syntax for man is "man -w <section> <name>", possibly omitting
885 section */
886 if (section) {
887 argv[2] = section;
888 argv[3] = name;
889 } else {
890 argv[2] = name;
893 if (!g_spawn_sync (NULL, argv, NULL,
894 G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
895 NULL, NULL,
896 &stdout, NULL, &status, &error)) {
897 g_warning ("Couldn't find path for %s(%s). Error: %s",
898 name, section, error->message);
899 g_error_free (error);
902 if (status == 0) {
903 lines = g_strsplit (stdout, "\n", 2);
904 g_free (stdout);
905 stdout = g_strdup (lines[0]);
907 g_strfreev (lines);
908 return stdout;
909 } else {
910 g_free (stdout);
911 return NULL;
915 static void
916 resolve_man_uri (YelpUri *uri)
918 YelpUriPrivate *priv = GET_PRIV (uri);
919 /* man:/path/to/file
920 * man:name(section)
921 * man:name.section
922 * man:name
925 /* Search via regular expressions for name, name(section) and
926 * name.section (assuming that name doesn't contain forward
927 * slashes or other nasties)
929 * If these don't match, assume that we were given a filename
930 * (absolute iff it starts with a /).
932 static GRegex* man_not_path = NULL;
933 GError *error = NULL;
934 GMatchInfo *match_info = NULL;
935 gchar *name, *section, *hash;
936 gchar *path;
938 if (!man_not_path) {
939 /* Match group 1 should contain the name; then one of groups 3
940 * and 4 will contain the section if there was one. Group 6
941 * will contain any hash fragment. */
942 man_not_path = g_regex_new ("man:([^ /.()#]+)"
943 "(\\(([0-9A-Za-z]+)\\)|\\.([0-9A-Za-z]+)|)"
944 "(#([^/ ()]+))?",
945 0, 0, &error);
946 if (!man_not_path) {
947 g_error ("Error with regex in man uri: %s\n",
948 error->message);
952 if (!g_regex_match (man_not_path, priv->res_arg,
953 0, &match_info)) {
954 /* The regexp didn't match, so treat as a file name. */
955 gchar *newuri;
956 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
957 newuri = g_strdup_printf ("file:%s", priv->res_arg + 4);
958 g_free (priv->res_arg);
959 priv->res_arg = newuri;
960 resolve_file_uri (uri);
962 else {
963 /* The regexp matched, so we've got a name/section pair that
964 * needs resolving. */
965 name = g_match_info_fetch (match_info, 1);
966 section = g_match_info_fetch (match_info, 3);
967 hash = g_match_info_fetch (match_info, 6);
968 if (!name) {
969 g_error ("Error matching strings in man uri '%s'",
970 priv->res_arg);
972 if ((!section) || (section[0] == '\0')) {
973 section = g_match_info_fetch (match_info, 4);
975 if (section && section[0] == '\0') section = NULL;
977 path = find_man_path (name, section);
979 if (!path) {
980 priv->tmptype = YELP_URI_DOCUMENT_TYPE_NOT_FOUND;
981 return;
983 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
984 priv->gfile = g_file_new_for_path (path);
985 priv->docuri = g_strdup ("man:");
986 priv->fulluri = g_strconcat ("man:", name, ".", section, NULL);
987 priv->page_id = g_strconcat (name, ".", section, NULL);
988 resolve_gfile (uri, NULL);
990 if (hash && hash[0] != '\0')
991 resolve_page_and_frag (uri, hash + 1);
993 g_free (path);
994 g_match_info_free (match_info);
999 Return 1 if ch is a number from 0 to 9 or a letter a-f or A-F and 0
1000 otherwise. This is sort of not utf8-safe, but since we are only
1001 looking for 7-bit things, it doesn't matter.
1003 static int
1004 is_hex (gchar ch)
1006 if (((48 <= ch) && (ch <= 57)) ||
1007 ((65 <= ch) && (ch <= 70)) ||
1008 ((97 <= ch) && (ch <= 102)))
1009 return 1;
1010 return 0;
1014 Return a newly allocated string, where %ab for a,b in [0, f] is
1015 replaced by the character it represents.
1017 static gchar*
1018 decode_url (const gchar *url)
1020 if (!url) return NULL;
1022 unsigned int len = strlen (url);
1023 int hex;
1024 gchar *ret = g_new (gchar, len + 1);
1025 const gchar *ptr = url, *end = url + len;
1026 gchar *retptr = ret, *tmp;
1028 while (ptr < end) {
1029 if (*ptr == '%' && is_hex(*(ptr + 1)) && is_hex(*(ptr + 2))) {
1030 *retptr = *(ptr+1);
1031 *(retptr+1) = *(ptr+2);
1032 *(retptr+2) = '\0';
1034 sscanf (retptr, "%x", &hex);
1036 if (hex < 0 || hex > 127) {
1037 g_warning ("Skipping non-7-bit character.");
1038 ptr++;
1039 continue;
1041 *retptr = (gchar)hex;
1043 retptr++;
1044 ptr += 3;
1046 else {
1047 tmp = g_utf8_next_char(ptr);
1048 memcpy (retptr, ptr, (tmp-ptr));
1049 retptr += tmp-ptr;
1050 ptr = tmp;
1053 *retptr = '\0';
1055 return ret;
1058 static void
1059 resolve_info_uri (YelpUri *uri)
1061 YelpUriPrivate *priv = GET_PRIV (uri);
1062 /* info:/path/to/file
1063 * info:name#node
1064 * info:name
1065 * info:(name)node
1066 * info:(name)
1068 static gchar **infopath = NULL;
1069 gchar *name = NULL;
1070 gchar *sect = NULL;
1071 gchar *fullpath = NULL;
1072 /* do not free */
1073 gchar *colon;
1074 gint infopath_i, suffix_i;
1076 if (g_str_has_prefix (priv->res_arg, "info:/")) {
1077 gchar *newuri;
1078 priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1079 newuri = g_strdup_printf ("file:%s", priv->res_arg + 4);
1080 g_free (priv->res_arg);
1081 priv->res_arg = newuri;
1082 resolve_file_uri (uri);
1083 return;
1086 if (!infopath) {
1087 /* Initialize infopath only once */
1089 /* Use the same logic as the info program. If INFOPATH is not
1090 specified, use the default. If it is specified, just use it
1091 unless it ends with a colon, in which case we add the
1092 default as a suffix.
1094 const gchar *env = g_getenv ("INFOPATH");
1095 gchar *paths;
1096 if (!env || env[0] == '\0')
1097 paths = g_strdup (default_info_path);
1098 else if (env[strlen (env)-1] == ':')
1099 paths = g_strconcat (env, default_info_path, NULL);
1100 else
1101 paths = g_strdup (env);
1103 infopath = g_strsplit (paths, ":", 0);
1105 g_free (paths);
1108 colon = strchr (priv->res_arg, ':');
1109 if (colon)
1110 colon++;
1111 else
1112 colon = (gchar *) priv->res_arg;
1114 if (colon[0] == '(') {
1115 const gchar *rbrace = strchr (colon, ')');
1116 if (rbrace) {
1117 name = g_strndup (colon + 1, rbrace - colon - 1);
1118 sect = g_strdup (rbrace + 1);
1121 else {
1122 const gchar *hash = strchr (colon, '#');
1123 if (hash) {
1124 name = g_strndup (colon, hash - colon);
1125 sect = g_strdup (hash + 1);
1127 else {
1128 name = g_strdup (colon);
1129 sect = NULL;
1133 for (infopath_i = 0; infopath[infopath_i]; infopath_i++) {
1134 if (!g_file_test (infopath[infopath_i], G_FILE_TEST_IS_DIR))
1135 continue;
1136 for (suffix_i = 0; infosuffix[suffix_i]; suffix_i++) {
1137 fullpath = g_strconcat (infopath[infopath_i], "/",
1138 name, infosuffix[suffix_i], NULL);
1139 if (g_file_test (fullpath, G_FILE_TEST_IS_REGULAR))
1140 break;
1141 g_free (fullpath);
1142 fullpath = NULL;
1144 if (fullpath != NULL)
1145 break;
1148 if (fullpath) {
1149 priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1150 priv->gfile = g_file_new_for_path (fullpath);
1151 priv->docuri = g_strconcat ("info:", name, NULL);
1152 if (sect) {
1153 priv->fulluri = g_strconcat ("info:", name, "#", sect, NULL);
1154 priv->page_id = g_strdup (sect);
1155 priv->frag_id = sect;
1156 sect = NULL; /* steal memory */
1158 else
1159 priv->fulluri = g_strdup (priv->docuri);
1160 resolve_gfile (uri, NULL);
1161 } else {
1162 gchar *res_arg = priv->res_arg;
1163 priv->res_arg = g_strconcat ("man:", name, NULL);
1164 resolve_man_uri (uri);
1165 if (priv->tmptype == YELP_URI_DOCUMENT_TYPE_MAN) {
1166 g_free (priv->res_arg);
1167 priv->res_arg = res_arg;
1169 else {
1170 g_free (res_arg);
1171 priv->tmptype = YELP_URI_DOCUMENT_TYPE_NOT_FOUND;
1174 g_free (fullpath);
1175 g_free (name);
1176 g_free (sect);
1179 static void
1180 resolve_xref_uri (YelpUri *uri)
1182 YelpUriPrivate *priv = GET_PRIV (uri);
1183 const gchar *arg = priv->res_arg + 5;
1184 YelpUriPrivate *base_priv = GET_PRIV (priv->res_base);
1186 priv->tmptype = base_priv->doctype;
1187 priv->gfile = g_object_ref (base_priv->gfile);
1188 priv->search_path = g_strdupv (base_priv->search_path);
1189 priv->docuri = g_strdup (base_priv->docuri);
1191 if (arg[0] == '#') {
1192 priv->page_id = g_strdup (base_priv->page_id);
1193 priv->frag_id = g_strdup (arg + 1);
1195 else {
1196 gchar *hash = strchr (arg, '#');
1197 if (hash) {
1198 priv->page_id = g_strndup (arg, hash - arg);
1199 priv->frag_id = g_strdup (hash + 1);
1201 else {
1202 priv->page_id = g_strdup (arg);
1203 priv->frag_id = NULL;
1206 if (priv->page_id && priv->page_id[0] == '\0') {
1207 g_free (priv->page_id);
1208 priv->page_id = NULL;
1211 if (priv->page_id &&
1212 g_str_has_prefix (priv->docuri, "info:")) {
1214 Special characters get url-encoded when they get clicked on
1215 as links. Info files, at least, don't want that so decode
1216 the url again here.
1218 gchar* tmp = priv->page_id;
1219 priv->page_id = decode_url (priv->page_id);
1220 g_free (tmp);
1223 if (g_str_has_prefix (priv->docuri, "ghelp:"))
1224 priv->fulluri = g_strconcat (priv->docuri,
1225 priv->page_id ? "?" : "",
1226 priv->page_id ? priv->page_id : "",
1227 priv->frag_id ? "#" : "",
1228 priv->frag_id ? priv->frag_id : "",
1229 NULL);
1230 else if (g_str_has_prefix (priv->docuri, "file:") ||
1231 g_str_has_prefix (priv->docuri, "info:") )
1232 priv->fulluri = g_strconcat (priv->docuri,
1233 (priv->page_id || priv->frag_id) ? "#" : "",
1234 priv->page_id ? priv->page_id : "",
1235 priv->frag_id ? "#" : "",
1236 priv->frag_id,
1237 NULL);
1238 else
1239 /* FIXME: other URI schemes */
1240 priv->fulluri = g_strconcat (priv->docuri,
1241 priv->page_id ? "#" : "",
1242 priv->page_id,
1243 NULL);
1246 static void
1247 resolve_page_and_frag (YelpUri *uri, const gchar *arg)
1249 YelpUriPrivate *priv = GET_PRIV (uri);
1250 gchar *hash;
1252 if (!arg || arg[0] == '\0')
1253 return;
1255 hash = strchr (arg, '#');
1256 if (hash) {
1257 priv->page_id = g_strndup (arg, hash - arg);
1258 priv->frag_id = g_strdup (hash + 1);
1259 } else {
1260 priv->page_id = g_strdup (arg);
1261 priv->frag_id = g_strdup (arg);
1263 return;
1266 static void
1267 resolve_gfile (YelpUri *uri, const gchar *hash)
1269 YelpUriPrivate *priv = GET_PRIV (uri);
1270 GFileInfo *info;
1271 GError *error = NULL;
1273 info = g_file_query_info (priv->gfile,
1274 G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1275 G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
1276 G_FILE_QUERY_INFO_NONE,
1277 NULL, &error);
1278 if (error) {
1279 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
1280 priv->tmptype = YELP_URI_DOCUMENT_TYPE_NOT_FOUND;
1282 else
1283 priv->tmptype = YELP_URI_DOCUMENT_TYPE_ERROR;
1284 g_error_free (error);
1285 return;
1288 if (priv->search_path == NULL) {
1289 if (g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE) ==
1290 G_FILE_TYPE_DIRECTORY) {
1291 priv->search_path = g_new0 (gchar *, 2);
1292 priv->search_path[0] = g_file_get_path (priv->gfile);
1293 } else {
1294 GFile *parent = g_file_get_parent (priv->gfile);
1295 priv->search_path = g_new0 (gchar *, 2);
1296 priv->search_path[0] = g_file_get_path (parent);
1297 g_object_unref (parent);
1301 if (priv->tmptype == YELP_URI_DOCUMENT_TYPE_UNRESOLVED) {
1302 gchar **splithash = NULL;
1303 if (hash)
1304 splithash = g_strsplit (hash, "#", 2);
1305 priv->tmptype = YELP_URI_DOCUMENT_TYPE_EXTERNAL;
1307 if (g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE) ==
1308 G_FILE_TYPE_DIRECTORY) {
1309 GFile *child = g_file_get_child (priv->gfile, "index.page");
1310 if (g_file_query_exists (child, NULL)) {
1311 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MALLARD;
1312 if (splithash) {
1313 if (priv->page_id == NULL)
1314 priv->page_id = g_strdup (splithash[0]);
1315 if (priv->frag_id == NULL && splithash[0])
1316 priv->frag_id = g_strdup (splithash[1]);
1319 else if (yelp_settings_get_editor_mode (yelp_settings_get_default ())) {
1320 g_object_unref (child);
1321 child = g_file_get_child (priv->gfile, "index.page.stub");
1322 if (g_file_query_exists (child, NULL)) {
1323 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MALLARD;
1324 if (splithash) {
1325 if (priv->page_id == NULL)
1326 priv->page_id = g_strdup (splithash[0]);
1327 if (priv->frag_id == NULL && splithash[0])
1328 priv->frag_id = g_strdup (splithash[1]);
1332 g_object_unref (child);
1334 else {
1335 gchar *basename;
1336 const gchar *mime_type = g_file_info_get_attribute_string (info,
1337 G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
1338 basename = g_file_get_basename (priv->gfile);
1339 if (g_str_has_suffix (basename, ".page")) {
1340 GFile *old;
1341 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MALLARD;
1342 old = priv->gfile;
1343 priv->gfile = g_file_get_parent (old);
1344 if (priv->page_id == NULL) {
1345 /* File names aren't really page IDs, so we stick an illegal character
1346 on the beginning so it can't possibly be confused with a real page
1347 ID. Then we let YelpMallardDocument map file names to pages IDs.
1349 gchar *tmp = g_file_get_basename (old);
1350 priv->page_id = g_strconcat (G_DIR_SEPARATOR_S, tmp, NULL);
1351 g_free (tmp);
1353 if (priv->frag_id == NULL)
1354 priv->frag_id = g_strdup (hash);
1355 g_object_unref (old);
1357 else if (g_str_equal (mime_type, "text/xml") ||
1358 g_str_equal (mime_type, "application/docbook+xml") ||
1359 g_str_equal (mime_type, "application/xml")) {
1360 priv->tmptype = YELP_URI_DOCUMENT_TYPE_DOCBOOK;
1361 if (splithash) {
1362 if (priv->page_id == NULL)
1363 priv->page_id = g_strdup (splithash[0]);
1364 if (priv->frag_id == NULL && splithash[0])
1365 priv->frag_id = g_strdup (splithash[1]);
1368 else if (g_str_equal (mime_type, "text/html") ||
1369 g_str_equal (mime_type, "application/xhtml+xml")) {
1370 GFile *parent = g_file_get_parent (priv->gfile);
1371 priv->docuri = g_file_get_uri (parent);
1372 g_object_unref (parent);
1373 priv->tmptype = mime_type[0] == 't' ? YELP_URI_DOCUMENT_TYPE_HTML : YELP_URI_DOCUMENT_TYPE_XHTML;
1374 if (priv->page_id == NULL)
1375 priv->page_id = g_file_get_basename (priv->gfile);
1376 if (priv->frag_id == NULL)
1377 priv->frag_id = g_strdup (hash);
1378 if (priv->fulluri == NULL) {
1379 gchar *fulluri;
1380 fulluri = g_file_get_uri (priv->gfile);
1381 priv->fulluri = g_strconcat (fulluri,
1382 priv->frag_id ? "#" : NULL,
1383 priv->frag_id,
1384 NULL);
1385 g_free (fulluri);
1388 else if (g_str_equal (mime_type, "application/x-gzip")) {
1389 if (g_str_has_suffix (basename, ".info.gz"))
1390 priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1391 else if (is_man_path (basename, "gz"))
1392 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1394 else if (g_str_equal (mime_type, "application/x-bzip")) {
1395 if (g_str_has_suffix (basename, ".info.bz2"))
1396 priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1397 else if (is_man_path (basename, "bz2"))
1398 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1400 else if (g_str_equal (mime_type, "application/x-lzma")) {
1401 if (g_str_has_suffix (basename, ".info.lzma"))
1402 priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1403 else if (is_man_path (basename, "lzma"))
1404 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1406 else if (g_str_equal (mime_type, "application/octet-stream")) {
1407 if (g_str_has_suffix (basename, ".info"))
1408 priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1409 else if (is_man_path (basename, NULL))
1410 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1412 else if (g_str_equal (mime_type, "text/plain")) {
1413 if (g_str_has_suffix (basename, ".info"))
1414 priv->tmptype = YELP_URI_DOCUMENT_TYPE_INFO;
1415 else if (is_man_path (basename, NULL))
1416 priv->tmptype = YELP_URI_DOCUMENT_TYPE_MAN;
1417 else
1418 priv->tmptype = YELP_URI_DOCUMENT_TYPE_TEXT;
1419 if (priv->frag_id == NULL)
1420 priv->frag_id = g_strdup (hash);
1422 else if (g_str_equal (mime_type, "text/x-readme")) {
1423 priv->tmptype = YELP_URI_DOCUMENT_TYPE_TEXT;
1425 else {
1426 priv->tmptype = YELP_URI_DOCUMENT_TYPE_EXTERNAL;
1429 if (splithash)
1430 g_strfreev (splithash);
1433 if (priv->docuri == NULL)
1434 priv->docuri = g_file_get_uri (priv->gfile);
1436 if (priv->fulluri == NULL)
1437 priv->fulluri = g_strconcat (priv->docuri,
1438 (priv->page_id || priv->frag_id) ? "#" : NULL,
1439 priv->page_id ? priv->page_id : "",
1440 priv->frag_id ? "#" : NULL,
1441 priv->frag_id ? priv->frag_id : NULL,
1442 NULL);
1444 g_object_unref (info);
1447 static gboolean
1448 is_man_path (const gchar *path, const gchar *encoding)
1450 gchar **iter = (gchar **) mancats;
1452 if (encoding && *encoding) {
1453 while (iter && *iter) {
1454 gchar *ending = g_strdup_printf ("%s.%s", *iter, encoding);
1455 if (g_str_has_suffix (path, ending)) {
1456 g_free (ending);
1457 return TRUE;
1459 g_free (ending);
1460 iter++;
1462 } else {
1463 while (iter && *iter) {
1464 if (g_str_has_suffix (path, *iter)) {
1465 return TRUE;
1467 iter++;
1470 return FALSE;