1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2 /* SPDX-FileCopyrightText: 2001-2002 Mikael Hallendal <micke@imendio.com>
3  * SPDX-FileCopyrightText: 2008 Imendio AB
4  * SPDX-FileCopyrightText: 2017, 2018 Sébastien Wilmet <swilmet@gnome.org>
5  * SPDX-License-Identifier: GPL-3.0-or-later
6  */
7 
8 #include "config.h"
9 #include "dh-link.h"
10 #include <string.h>
11 #include <glib/gi18n-lib.h>
12 
13 /**
14  * SECTION:dh-link
15  * @Title: DhLink
16  * @Short_description: A link inside a #DhBook
17  *
18  * A #DhLink represents a link to an HTML page or somewhere inside a page (with
19  * an anchor) that is inside a #DhBook. The link can point to a specific symbol,
20  * or a page, or the top-level page of the #DhBook.
21  *
22  * A #DhLink has a type that can be retrieved with dh_link_get_link_type().
23  *
24  * There is exactly one #DhLink of type %DH_LINK_TYPE_BOOK per #DhBook object.
25  */
26 
27 /* Fields used only by DhLink's of type DH_LINK_TYPE_BOOK. */
28 typedef struct {
29         gchar *base_path;
30         gchar *book_id;
31 } BookData;
32 
33 struct _DhLink {
34         /* To avoid some memory padding inside the struct, to use less memory,
35          * the fields are placed in this order:
36          * 1. All the pointers.
37          * 2. Other types.
38          * 3. Bit fields.
39          *
40          * Also, a union is used to use less memory. This struct is allocated a
41          * lot, so it is worth optimizing it.
42          */
43 
44         union {
45                 /* @book.data is set only for links of @type DH_LINK_TYPE_BOOK. */
46                 BookData *data;
47 
48                 /* @book.link is set only for links of @type != DH_LINK_TYPE_BOOK. */
49                 DhLink *link;
50         } book;
51 
52         gchar *name;
53         gchar *name_collation_key;
54 
55         gchar *relative_url;
56 
57         guint ref_count;
58 
59         DhLinkType type : 8;
60         DhLinkFlags flags : 8;
61 };
62 
63 /* If the relative_url is empty. */
64 #define DEFAULT_PAGE "index.html"
65 
G_DEFINE_BOXED_TYPE(DhLink,dh_link,dh_link_ref,dh_link_unref)66 G_DEFINE_BOXED_TYPE (DhLink, dh_link,
67                      dh_link_ref, dh_link_unref)
68 
69 static BookData *
70 book_data_new (const gchar *base_path,
71                const gchar *book_id)
72 {
73         BookData *data;
74 
75         data = g_slice_new (BookData);
76         data->base_path = g_strdup (base_path);
77         data->book_id = g_strdup (book_id);
78 
79         return data;
80 }
81 
82 static void
book_data_free(BookData * data)83 book_data_free (BookData *data)
84 {
85         if (data == NULL)
86                 return;
87 
88         g_free (data->base_path);
89         g_free (data->book_id);
90         g_slice_free (BookData, data);
91 }
92 
93 static void
link_free(DhLink * link)94 link_free (DhLink *link)
95 {
96         if (link->type == DH_LINK_TYPE_BOOK)
97                 book_data_free (link->book.data);
98         else
99                 dh_link_unref (link->book.link);
100 
101         g_free (link->name);
102         g_free (link->name_collation_key);
103         g_free (link->relative_url);
104 
105         g_slice_free (DhLink, link);
106 }
107 
108 static DhLink *
dh_link_new_common(DhLinkType type,const gchar * name,const gchar * relative_url)109 dh_link_new_common (DhLinkType   type,
110                     const gchar *name,
111                     const gchar *relative_url)
112 {
113         DhLink *link;
114 
115         link = g_slice_new0 (DhLink);
116         link->ref_count = 1;
117         link->type = type;
118         link->name = g_strdup (name);
119         link->relative_url = g_strdup (relative_url);
120 
121         return link;
122 }
123 
124 /**
125  * dh_link_new_book:
126  * @base_path: the base path for the book.
127  * @book_id: the book ID.
128  * @book_title: the name of the link.
129  * @relative_url: the URL relative to the book @base_path. Can contain an
130  * anchor. Usually the index.html page.
131  *
132  * Returns: a new #DhLink of type %DH_LINK_TYPE_BOOK.
133  * Since: 3.28
134  */
135 DhLink *
dh_link_new_book(const gchar * base_path,const gchar * book_id,const gchar * book_title,const gchar * relative_url)136 dh_link_new_book (const gchar *base_path,
137                   const gchar *book_id,
138                   const gchar *book_title,
139                   const gchar *relative_url)
140 {
141         DhLink *link;
142 
143         g_return_val_if_fail (base_path != NULL, NULL);
144         g_return_val_if_fail (book_id != NULL, NULL);
145         g_return_val_if_fail (book_title != NULL, NULL);
146         g_return_val_if_fail (relative_url != NULL, NULL);
147 
148         link = dh_link_new_common (DH_LINK_TYPE_BOOK, book_title, relative_url);
149 
150         link->book.data = book_data_new (base_path, book_id);
151 
152         return link;
153 }
154 
155 /**
156  * dh_link_new:
157  * @type: the #DhLinkType. Must be different than %DH_LINK_TYPE_BOOK.
158  * @book_link: the #DhLink of type %DH_LINK_TYPE_BOOK for the book that the link
159  *   is contained in.
160  * @name: the name of the link.
161  * @relative_url: the URL relative to the book base path. Can contain an anchor.
162  *
163  * Returns: a new #DhLink.
164  */
165 DhLink *
dh_link_new(DhLinkType type,DhLink * book_link,const gchar * name,const gchar * relative_url)166 dh_link_new (DhLinkType   type,
167              DhLink      *book_link,
168              const gchar *name,
169              const gchar *relative_url)
170 {
171         DhLink *link;
172 
173         g_return_val_if_fail (type != DH_LINK_TYPE_BOOK, NULL);
174         g_return_val_if_fail (book_link != NULL, NULL);
175         g_return_val_if_fail (book_link->type == DH_LINK_TYPE_BOOK, NULL);
176         g_return_val_if_fail (name != NULL, NULL);
177         g_return_val_if_fail (relative_url != NULL, NULL);
178 
179         link = dh_link_new_common (type, name, relative_url);
180 
181         link->book.link = dh_link_ref (book_link);
182 
183         return link;
184 }
185 
186 /**
187  * dh_link_ref:
188  * @link: a #DhLink.
189  *
190  * Increases the reference count of @link.
191  *
192  * Not thread-safe.
193  *
194  * Returns: (transfer full): the @link.
195  */
196 DhLink *
dh_link_ref(DhLink * link)197 dh_link_ref (DhLink *link)
198 {
199         g_return_val_if_fail (link != NULL, NULL);
200 
201         link->ref_count++;
202 
203         return link;
204 }
205 
206 /**
207  * dh_link_unref:
208  * @link: a #DhLink.
209  *
210  * Decreases the reference count of @link.
211  *
212  * Not thread-safe.
213  */
214 void
dh_link_unref(DhLink * link)215 dh_link_unref (DhLink *link)
216 {
217         g_return_if_fail (link != NULL);
218 
219         if (link->ref_count == 1)
220                 link_free (link);
221         else
222                 link->ref_count--;
223 }
224 
225 /**
226  * dh_link_get_link_type:
227  * @link: a #DhLink.
228  *
229  * Returns: the #DhLinkType of @link.
230  */
231 DhLinkType
dh_link_get_link_type(DhLink * link)232 dh_link_get_link_type (DhLink *link)
233 {
234         g_return_val_if_fail (link != NULL, 0);
235 
236         return link->type;
237 }
238 
239 /**
240  * dh_link_get_flags:
241  * @link: a #DhLink.
242  *
243  * Returns: the #DhLinkFlags of @link.
244  */
245 DhLinkFlags
dh_link_get_flags(DhLink * link)246 dh_link_get_flags (DhLink *link)
247 {
248         g_return_val_if_fail (link != NULL, DH_LINK_FLAGS_NONE);
249 
250         return link->flags;
251 }
252 
253 /**
254  * dh_link_set_flags:
255  * @link: a #DhLink.
256  * @flags: the new flags of the link.
257  *
258  * Sets the flags of the link.
259  */
260 void
dh_link_set_flags(DhLink * link,DhLinkFlags flags)261 dh_link_set_flags (DhLink      *link,
262                    DhLinkFlags  flags)
263 {
264         g_return_if_fail (link != NULL);
265 
266         link->flags = flags;
267 }
268 
269 /**
270  * dh_link_get_name:
271  * @link: a #DhLink.
272  *
273  * Returns: the name of the @link. For a link of type %DH_LINK_TYPE_BOOK,
274  * returns the book title.
275  */
276 const gchar *
dh_link_get_name(DhLink * link)277 dh_link_get_name (DhLink *link)
278 {
279         g_return_val_if_fail (link != NULL, NULL);
280 
281         return link->name;
282 }
283 
284 /**
285  * dh_link_match_relative_url:
286  * @link: a #DhLink.
287  * @relative_url: an URL relative to the book base path. Can contain an anchor.
288  *
289  * Returns: whether the relative URL of @link matches with @relative_url. There
290  * is a special case for the index.html page, it can also match the empty
291  * string.
292  * Since: 3.28
293  */
294 gboolean
dh_link_match_relative_url(DhLink * link,const gchar * relative_url)295 dh_link_match_relative_url (DhLink      *link,
296                             const gchar *relative_url)
297 {
298         g_return_val_if_fail (link != NULL, FALSE);
299         g_return_val_if_fail (link->relative_url != NULL, FALSE);
300         g_return_val_if_fail (relative_url != NULL, FALSE);
301 
302         if (g_str_equal (link->relative_url, relative_url))
303                 return TRUE;
304 
305         /* Special case for index.html, can also match the empty string.
306          * Example of full URLs:
307          * file:///usr/share/gtk-doc/html/glib/
308          * file:///usr/share/gtk-doc/html/glib/index.html
309          *
310          * This supports only the root index.html page of a DhBook, this doesn't
311          * support index.html inside a sub-directory, if the relative_url
312          * contains a sub-directory. But apparently GTK-Doc doesn't create
313          * sub-directories, all the *.html pages are in the same directory.
314          */
315         if (relative_url[0] == '\0')
316                 return g_str_equal (link->relative_url, DEFAULT_PAGE);
317         else if (link->relative_url[0] == '\0')
318                 return g_str_equal (relative_url, DEFAULT_PAGE);
319 
320         return FALSE;
321 }
322 
323 /**
324  * dh_link_belongs_to_page:
325  * @link: a #DhLink.
326  * @page_id: a page ID, i.e. the filename without its extension.
327  *
328  * This function permits to know if @link belongs to a certain page.
329  *
330  * @page_id is usually the HTML filename without the `.html` extension. More
331  * generally, @page_id must be a relative URL (relative to the book base path),
332  * without the anchor nor the file extension.
333  *
334  * For example if @link has the relative URL `"DhLink.html#dh-link-ref"`, then
335  * this function will return %TRUE if the @page_id is `"DhLink"`.
336  *
337  * Returns: whether @link belongs to @page_id.
338  * Since: 3.28
339  */
340 gboolean
dh_link_belongs_to_page(DhLink * link,const gchar * page_id)341 dh_link_belongs_to_page (DhLink      *link,
342                          const gchar *page_id)
343 {
344         const gchar *relative_url;
345         gsize page_id_len;
346 
347         g_return_val_if_fail (link != NULL, FALSE);
348         g_return_val_if_fail (link->relative_url != NULL, FALSE);
349         g_return_val_if_fail (page_id != NULL, FALSE);
350 
351         relative_url = link->relative_url;
352         if (relative_url[0] == '\0')
353                 relative_url = DEFAULT_PAGE;
354 
355         page_id_len = strlen (page_id);
356 
357         /* Check that a file extension follows. */
358         return (g_str_has_prefix (relative_url, page_id) &&
359                 relative_url[page_id_len] == '.');
360 }
361 
362 /**
363  * dh_link_get_uri:
364  * @link: a #DhLink.
365  *
366  * Gets the @link URI, by concateneting the book base path with the @link
367  * relative URL.
368  *
369  * Returns: (nullable): the @link URI, or %NULL if getting the URI failed. Free
370  * with g_free() when no longer needed.
371  */
372 gchar *
dh_link_get_uri(DhLink * link)373 dh_link_get_uri (DhLink *link)
374 {
375         const gchar *base_path;
376         gchar *filename;
377         gchar *uri;
378         gchar *anchor;
379         GError *error = NULL;
380 
381         g_return_val_if_fail (link != NULL, NULL);
382 
383         if (link->type == DH_LINK_TYPE_BOOK)
384                 base_path = link->book.data->base_path;
385         else
386                 base_path = link->book.link->book.data->base_path;
387 
388         filename = g_build_filename (base_path, link->relative_url, NULL);
389 
390         anchor = strrchr (filename, '#');
391         if (anchor != NULL) {
392                 *anchor = '\0';
393                 anchor++;
394         }
395 
396         uri = g_filename_to_uri (filename, NULL, &error);
397         if (error != NULL) {
398                 g_warning ("Failed to get DhLink URI: %s", error->message);
399                 g_clear_error (&error);
400         }
401 
402         if (uri != NULL && anchor != NULL) {
403                 gchar *uri_with_anchor;
404 
405                 uri_with_anchor = g_strconcat (uri, "#", anchor, NULL);
406                 g_free (uri);
407                 uri = uri_with_anchor;
408         }
409 
410         g_free (filename);
411         return uri;
412 }
413 
414 /**
415  * dh_link_get_book_title:
416  * @link: a #DhLink.
417  *
418  * Returns: the title of the book that the @link is contained in.
419  */
420 const gchar *
dh_link_get_book_title(DhLink * link)421 dh_link_get_book_title (DhLink *link)
422 {
423         g_return_val_if_fail (link != NULL, NULL);
424 
425         if (link->type == DH_LINK_TYPE_BOOK)
426                 return link->name;
427 
428         if (link->book.link != NULL)
429                 return link->book.link->name;
430 
431         return "";
432 }
433 
434 /**
435  * dh_link_get_book_id:
436  * @link: a #DhLink.
437  *
438  * Returns: the ID of the book that the @link is contained in.
439  */
440 const gchar *
dh_link_get_book_id(DhLink * link)441 dh_link_get_book_id (DhLink *link)
442 {
443         g_return_val_if_fail (link != NULL, NULL);
444 
445         if (link->type == DH_LINK_TYPE_BOOK)
446                 return link->book.data->book_id;
447 
448         if (link->book.link != NULL)
449                 return link->book.link->book.data->book_id;
450 
451         return "";
452 }
453 
454 static gint
dh_link_type_compare(DhLinkType a,DhLinkType b)455 dh_link_type_compare (DhLinkType a,
456                       DhLinkType b)
457 {
458         if (a == b)
459                 return 0;
460 
461         /* Same order as in a tree: first the top-level book node, then pages,
462          * then keywords (keywords are contained in a page).
463          */
464 
465         if (a == DH_LINK_TYPE_BOOK)
466                 return -1;
467         if (b == DH_LINK_TYPE_BOOK)
468                 return 1;
469 
470         if (a == DH_LINK_TYPE_PAGE)
471                 return -1;
472         if (b == DH_LINK_TYPE_PAGE)
473                 return 1;
474 
475         return 0;
476 }
477 
478 /**
479  * dh_link_compare:
480  * @a: (type DhLink): a #DhLink.
481  * @b: (type DhLink): a #DhLink.
482  *
483  * Compares the links @a and @b. This function is used to determine in which
484  * order the links should be displayed.
485  *
486  * Returns: an integer less than zero if @a should appear before @b; zero if
487  * there are no preferences; an integer greater than zero if @b should appear
488  * before @a.
489  */
490 gint
dh_link_compare(gconstpointer a,gconstpointer b)491 dh_link_compare (gconstpointer a,
492                  gconstpointer b)
493 {
494         DhLink *la = (DhLink *) a;
495         DhLink *lb = (DhLink *) b;
496         gint flags_diff;
497         gint diff;
498 
499         g_return_val_if_fail (a != NULL, 0);
500         g_return_val_if_fail (b != NULL, 0);
501 
502         /* Sort deprecated hits last. */
503         flags_diff = ((la->flags & DH_LINK_FLAGS_DEPRECATED) -
504                       (lb->flags & DH_LINK_FLAGS_DEPRECATED));
505         if (flags_diff != 0)
506                 return flags_diff;
507 
508         /* Collation-based sorting */
509         if (G_UNLIKELY (la->name_collation_key == NULL))
510                 la->name_collation_key = g_utf8_collate_key (la->name, -1);
511         if (G_UNLIKELY (lb->name_collation_key == NULL))
512                 lb->name_collation_key = g_utf8_collate_key (lb->name, -1);
513 
514         diff = strcmp (la->name_collation_key,
515                        lb->name_collation_key);
516 
517         if (diff != 0)
518                 return diff;
519 
520         return dh_link_type_compare (la->type, lb->type);
521 }
522 
523 /**
524  * dh_link_type_to_string:
525  * @link_type: a #DhLinkType.
526  *
527  * Returns: a string representation of the #DhLinkType, translated in the
528  * current language.
529  */
530 const gchar *
dh_link_type_to_string(DhLinkType link_type)531 dh_link_type_to_string (DhLinkType link_type)
532 {
533         switch (link_type) {
534         case DH_LINK_TYPE_BOOK:
535                 /* i18n: a documentation book */
536                 return _("Book");
537 
538         case DH_LINK_TYPE_PAGE:
539                 /* i18n: a "page" in a documentation book */
540                 return _("Page");
541 
542         case DH_LINK_TYPE_KEYWORD:
543                 /* i18n: a search hit in the documentation, could be a
544                  * function, macro, struct, etc */
545                 return _("Keyword");
546 
547         case DH_LINK_TYPE_FUNCTION:
548                 /* i18n: in the programming language context, if you don't
549                  * have an ESTABLISHED term for it, leave it
550                  * untranslated. */
551                 return _("Function");
552 
553         case DH_LINK_TYPE_STRUCT:
554                 /* i18n: in the programming language context, if you don't
555                  * have an ESTABLISHED term for it, leave it
556                  * untranslated. */
557                 return _("Struct");
558 
559         case DH_LINK_TYPE_MACRO:
560                 /* i18n: in the programming language context, if you don't
561                  * have an ESTABLISHED term for it, leave it
562                  * untranslated. */
563                 return _("Macro");
564 
565         case DH_LINK_TYPE_ENUM:
566                 /* i18n: in the programming language context, if you don't
567                  * have an ESTABLISHED term for it, leave it
568                  * untranslated. */
569                 return _("Enum");
570 
571         case DH_LINK_TYPE_TYPEDEF:
572                 /* i18n: in the programming language context, if you don't
573                  * have an ESTABLISHED term for it, leave it
574                  * untranslated. */
575                 return _("Type");
576 
577         case DH_LINK_TYPE_PROPERTY:
578                 /* i18n: in the programming language context, if you don't
579                  * have an ESTABLISHED term for it, leave it
580                  * untranslated. */
581                 return _("Property");
582 
583         case DH_LINK_TYPE_SIGNAL:
584                 /* i18n: in the programming language context, if you don't
585                  * have an ESTABLISHED term for it, leave it
586                  * untranslated. */
587                 return _("Signal");
588 
589         default:
590                 break;
591         }
592 
593         g_return_val_if_reached ("");
594 }
595