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