dh-link.c 16.4 KB
Newer Older
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
Mikael Hallendal's avatar
Mikael Hallendal committed
2
/*
3
 * Copyright (C) 2001-2002 Mikael Hallendal <micke@imendio.com>
4
 * Copyright (C) 2008 Imendio AB
5
 * Copyright (C) 2017, 2018 Sébastien Wilmet <swilmet@gnome.org>
Mikael Hallendal's avatar
Mikael Hallendal committed
6 7 8 9 10 11 12 13 14 15 16
 *
 * 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.
 *
17 18
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
Mikael Hallendal's avatar
Mikael Hallendal committed
19 20
 */

21
#include "config.h"
22
#include "dh-link.h"
23
#include <string.h>
24
#include <glib/gi18n-lib.h>
Mikael Hallendal's avatar
Mikael Hallendal committed
25

26 27 28 29 30 31 32
/**
 * SECTION:dh-link
 * @Title: DhLink
 * @Short_description: A link inside a #DhBook
 *
 * A #DhLink represents a link to an HTML page or somewhere inside a page (with
 * an anchor) that is inside a #DhBook. The link can point to a specific symbol,
33
 * or a page, or the top-level page of the #DhBook.
34 35
 *
 * A #DhLink has a type that can be retrieved with dh_link_get_link_type().
36 37
 *
 * There is exactly one #DhLink of type %DH_LINK_TYPE_BOOK per #DhBook object.
38 39
 */

40 41 42 43 44 45
/* Fields used only by DhLink's of type DH_LINK_TYPE_BOOK. */
typedef struct {
        gchar *base_path;
        gchar *book_id;
} BookData;

46
struct _DhLink {
47 48 49 50 51
        /* To avoid some memory padding inside the struct, to use less memory,
         * the fields are placed in this order:
         * 1. All the pointers.
         * 2. Other types.
         * 3. Bit fields.
52 53 54
         *
         * Also, a union is used to use less memory. This struct is allocated a
         * lot, so it is worth optimizing it.
55 56
         */

57 58 59
        union {
                /* @book.data is set only for links of @type DH_LINK_TYPE_BOOK. */
                BookData *data;
60

61 62 63
                /* @book.link is set only for links of @type != DH_LINK_TYPE_BOOK. */
                DhLink *link;
        } book;
64

65 66
        gchar *name;
        gchar *name_collation_key;
67

68
        gchar *relative_url;
69

70 71
        guint ref_count;

72 73
        DhLinkType type : 8;
        DhLinkFlags flags : 8;
74 75
};

76 77 78
/* If the relative_url is empty. */
#define DEFAULT_PAGE "index.html"

79 80
G_DEFINE_BOXED_TYPE (DhLink, dh_link,
                     dh_link_ref, dh_link_unref)
Mikael Hallendal's avatar
Mikael Hallendal committed
81

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
static BookData *
book_data_new (const gchar *base_path,
               const gchar *book_id)
{
        BookData *data;

        data = g_slice_new (BookData);
        data->base_path = g_strdup (base_path);
        data->book_id = g_strdup (book_id);

        return data;
}

static void
book_data_free (BookData *data)
{
        if (data == NULL)
                return;

        g_free (data->base_path);
        g_free (data->book_id);
        g_slice_free (BookData, data);
}

Richard Hult's avatar
Richard Hult committed
106
static void
Mikael Hallendal's avatar
Mikael Hallendal committed
107 108
link_free (DhLink *link)
{
109 110 111 112
        if (link->type == DH_LINK_TYPE_BOOK)
                book_data_free (link->book.data);
        else
                dh_link_unref (link->book.link);
Mikael Hallendal's avatar
Mikael Hallendal committed
113

114 115 116 117
        g_free (link->name);
        g_free (link->name_collation_key);
        g_free (link->relative_url);

118
        g_slice_free (DhLink, link);
Mikael Hallendal's avatar
Mikael Hallendal committed
119 120
}

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
static DhLink *
dh_link_new_common (DhLinkType   type,
                    const gchar *name,
                    const gchar *relative_url)
{
        DhLink *link;

        link = g_slice_new0 (DhLink);
        link->ref_count = 1;
        link->type = type;
        link->name = g_strdup (name);
        link->relative_url = g_strdup (relative_url);

        return link;
}

137
/**
138 139 140
 * dh_link_new_book:
 * @base_path: the base path for the book.
 * @book_id: the book ID.
141
 * @book_title: the name of the link.
142
 * @relative_url: the URL relative to the book @base_path. Can contain an
143
 * anchor. Usually the index.html page.
144
 *
145 146 147 148 149 150
 * Returns: a new #DhLink of type %DH_LINK_TYPE_BOOK.
 * Since: 3.28
 */
DhLink *
dh_link_new_book (const gchar *base_path,
                  const gchar *book_id,
151
                  const gchar *book_title,
152 153 154 155 156 157
                  const gchar *relative_url)
{
        DhLink *link;

        g_return_val_if_fail (base_path != NULL, NULL);
        g_return_val_if_fail (book_id != NULL, NULL);
158
        g_return_val_if_fail (book_title != NULL, NULL);
159 160
        g_return_val_if_fail (relative_url != NULL, NULL);

161
        link = dh_link_new_common (DH_LINK_TYPE_BOOK, book_title, relative_url);
162

163
        link->book.data = book_data_new (base_path, book_id);
164 165 166 167 168 169 170

        return link;
}

/**
 * dh_link_new:
 * @type: the #DhLinkType. Must be different than %DH_LINK_TYPE_BOOK.
171 172
 * @book_link: the #DhLink of type %DH_LINK_TYPE_BOOK for the book that the link
 *   is contained in.
173
 * @name: the name of the link.
174
 * @relative_url: the URL relative to the book base path. Can contain an anchor.
175
 *
176
 * Returns: a new #DhLink.
177
 */
Mikael Hallendal's avatar
Mikael Hallendal committed
178
DhLink *
Richard Hult's avatar
Richard Hult committed
179
dh_link_new (DhLinkType   type,
180
             DhLink      *book_link,
181
             const gchar *name,
182
             const gchar *relative_url)
Mikael Hallendal's avatar
Mikael Hallendal committed
183
{
184
        DhLink *link;
Mikael Hallendal's avatar
Mikael Hallendal committed
185

186
        g_return_val_if_fail (type != DH_LINK_TYPE_BOOK, NULL);
187 188
        g_return_val_if_fail (book_link != NULL, NULL);
        g_return_val_if_fail (book_link->type == DH_LINK_TYPE_BOOK, NULL);
189 190 191
        g_return_val_if_fail (name != NULL, NULL);
        g_return_val_if_fail (relative_url != NULL, NULL);

192
        link = dh_link_new_common (type, name, relative_url);
Mikael Hallendal's avatar
Mikael Hallendal committed
193

194
        link->book.link = dh_link_ref (book_link);
195

196
        return link;
Mikael Hallendal's avatar
Mikael Hallendal committed
197 198
}

199 200
/**
 * dh_link_ref:
201
 * @link: a #DhLink.
202
 *
203
 * Increases the reference count of @link.
204
 *
205 206
 * Not thread-safe.
 *
207
 * Returns: (transfer full): the @link.
208
 */
Mikael Hallendal's avatar
Mikael Hallendal committed
209 210 211
DhLink *
dh_link_ref (DhLink *link)
{
212
        g_return_val_if_fail (link != NULL, NULL);
Mikael Hallendal's avatar
Mikael Hallendal committed
213

214
        link->ref_count++;
Richard Hult's avatar
Richard Hult committed
215

216
        return link;
Mikael Hallendal's avatar
Mikael Hallendal committed
217 218
}

219 220
/**
 * dh_link_unref:
221
 * @link: a #DhLink.
222
 *
223
 * Decreases the reference count of @link.
224 225
 *
 * Not thread-safe.
226
 */
Mikael Hallendal's avatar
Mikael Hallendal committed
227 228 229
void
dh_link_unref (DhLink *link)
{
230
        g_return_if_fail (link != NULL);
Mikael Hallendal's avatar
Mikael Hallendal committed
231

232
        if (link->ref_count == 1)
233
                link_free (link);
234 235
        else
                link->ref_count--;
Mikael Hallendal's avatar
Mikael Hallendal committed
236
}
237

238
/**
239
 * dh_link_get_link_type:
240
 * @link: a #DhLink.
241
 *
242
 * Returns: the #DhLinkType of @link.
243
 */
244 245
DhLinkType
dh_link_get_link_type (DhLink *link)
246
{
247
        g_return_val_if_fail (link != NULL, 0);
248

249
        return link->type;
250 251
}

252
/**
253
 * dh_link_get_flags:
254
 * @link: a #DhLink.
255
 *
256
 * Returns: the #DhLinkFlags of @link.
257
 */
258 259
DhLinkFlags
dh_link_get_flags (DhLink *link)
260
{
261
        g_return_val_if_fail (link != NULL, DH_LINK_FLAGS_NONE);
262

263 264
        return link->flags;
}
265

266 267 268 269 270 271 272 273 274 275 276 277 278 279
/**
 * dh_link_set_flags:
 * @link: a #DhLink.
 * @flags: the new flags of the link.
 *
 * Sets the flags of the link.
 */
void
dh_link_set_flags (DhLink      *link,
                   DhLinkFlags  flags)
{
        g_return_if_fail (link != NULL);

        link->flags = flags;
280 281
}

282
/**
283
 * dh_link_get_name:
284
 * @link: a #DhLink.
285
 *
286 287
 * Returns: the name of the @link. For a link of type %DH_LINK_TYPE_BOOK,
 * returns the book title.
288
 */
289
const gchar *
290
dh_link_get_name (DhLink *link)
291
{
292 293
        g_return_val_if_fail (link != NULL, NULL);

294
        return link->name;
295 296
}

297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
/**
 * dh_link_match_relative_url:
 * @link: a #DhLink.
 * @relative_url: an URL relative to the book base path. Can contain an anchor.
 *
 * Returns: whether the relative URL of @link matches with @relative_url. There
 * is a special case for the index.html page, it can also match the empty
 * string.
 * Since: 3.28
 */
gboolean
dh_link_match_relative_url (DhLink      *link,
                            const gchar *relative_url)
{
        g_return_val_if_fail (link != NULL, FALSE);
        g_return_val_if_fail (link->relative_url != NULL, FALSE);
        g_return_val_if_fail (relative_url != NULL, FALSE);

        if (g_str_equal (link->relative_url, relative_url))
                return TRUE;

        /* Special case for index.html, can also match the empty string.
         * Example of full URLs:
         * file:///usr/share/gtk-doc/html/glib/
         * file:///usr/share/gtk-doc/html/glib/index.html
         *
         * This supports only the root index.html page of a DhBook, this doesn't
         * support index.html inside a sub-directory, if the relative_url
         * contains a sub-directory. But apparently GTK-Doc doesn't create
         * sub-directories, all the *.html pages are in the same directory.
         */
        if (relative_url[0] == '\0')
329
                return g_str_equal (link->relative_url, DEFAULT_PAGE);
330
        else if (link->relative_url[0] == '\0')
331
                return g_str_equal (relative_url, DEFAULT_PAGE);
332 333 334 335

        return FALSE;
}

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
/**
 * dh_link_belongs_to_page:
 * @link: a #DhLink.
 * @page_id: a page ID, i.e. the filename without its extension.
 *
 * This function permits to know if @link belongs to a certain page.
 *
 * @page_id is usually the HTML filename without the `.html` extension. More
 * generally, @page_id must be a relative URL (relative to the book base path),
 * without the anchor nor the file extension.
 *
 * For example if @link has the relative URL `"DhLink.html#dh-link-ref"`, then
 * this function will return %TRUE if the @page_id is `"DhLink"`.
 *
 * Returns: whether @link belongs to @page_id.
 * Since: 3.28
 */
gboolean
dh_link_belongs_to_page (DhLink      *link,
355
                         const gchar *page_id)
356 357 358 359 360 361 362 363 364 365
{
        const gchar *relative_url;
        gsize page_id_len;

        g_return_val_if_fail (link != NULL, FALSE);
        g_return_val_if_fail (link->relative_url != NULL, FALSE);
        g_return_val_if_fail (page_id != NULL, FALSE);

        relative_url = link->relative_url;
        if (relative_url[0] == '\0')
366
                relative_url = DEFAULT_PAGE;
367 368 369 370

        page_id_len = strlen (page_id);

        /* Check that a file extension follows. */
371 372
        return (g_str_has_prefix (relative_url, page_id) &&
                relative_url[page_id_len] == '.');
373 374
}

375 376
/**
 * dh_link_get_uri:
377
 * @link: a #DhLink.
378
 *
379 380 381 382 383
 * Gets the @link URI, by concateneting the book base path with the @link
 * relative URL.
 *
 * Returns: (nullable): the @link URI, or %NULL if getting the URI failed. Free
 * with g_free() when no longer needed.
384
 */
385
gchar *
386 387
dh_link_get_uri (DhLink *link)
{
388
        const gchar *base_path;
389 390 391
        gchar *filename;
        gchar *uri;
        gchar *anchor;
392
        GError *error = NULL;
393

394 395
        g_return_val_if_fail (link != NULL, NULL);

396
        if (link->type == DH_LINK_TYPE_BOOK)
397
                base_path = link->book.data->base_path;
398
        else
399
                base_path = link->book.link->book.data->base_path;
400

401
        filename = g_build_filename (base_path, link->relative_url, NULL);
402 403

        anchor = strrchr (filename, '#');
404
        if (anchor != NULL) {
405 406 407 408
                *anchor = '\0';
                anchor++;
        }

409 410 411 412 413
        uri = g_filename_to_uri (filename, NULL, &error);
        if (error != NULL) {
                g_warning ("Failed to get DhLink URI: %s", error->message);
                g_clear_error (&error);
        }
414

415
        if (uri != NULL && anchor != NULL) {
416 417 418 419 420 421
                gchar *uri_with_anchor;

                uri_with_anchor = g_strconcat (uri, "#", anchor, NULL);
                g_free (uri);
                uri = uri_with_anchor;
        }
422

423
        g_free (filename);
424
        return uri;
425 426
}

427
/**
428
 * dh_link_get_book_title:
429
 * @link: a #DhLink.
430
 *
431
 * Returns: the title of the book that the @link is contained in.
432
 */
433
const gchar *
434
dh_link_get_book_title (DhLink *link)
435
{
436
        g_return_val_if_fail (link != NULL, NULL);
437

438 439 440
        if (link->type == DH_LINK_TYPE_BOOK)
                return link->name;

441 442
        if (link->book.link != NULL)
                return link->book.link->name;
443 444

        return "";
445 446
}

447
/**
448
 * dh_link_get_book_id:
449
 * @link: a #DhLink.
450
 *
451
 * Returns: the ID of the book that the @link is contained in.
452
 */
453 454
const gchar *
dh_link_get_book_id (DhLink *link)
455
{
456
        g_return_val_if_fail (link != NULL, NULL);
457

458
        if (link->type == DH_LINK_TYPE_BOOK)
459
                return link->book.data->book_id;
460

461 462
        if (link->book.link != NULL)
                return link->book.link->book.data->book_id;
463 464

        return "";
465 466
}

467 468 469 470 471 472 473
static gint
dh_link_type_compare (DhLinkType a,
                      DhLinkType b)
{
        if (a == b)
                return 0;

474 475
        /* Same order as in a tree: first the top-level book node, then pages,
         * then keywords (keywords are contained in a page).
476
         */
477 478

        if (a == DH_LINK_TYPE_BOOK)
479
                return -1;
480 481
        if (b == DH_LINK_TYPE_BOOK)
                return 1;
482

483 484
        if (a == DH_LINK_TYPE_PAGE)
                return -1;
485 486 487 488 489 490
        if (b == DH_LINK_TYPE_PAGE)
                return 1;

        return 0;
}

491
/**
492 493 494
 * dh_link_compare:
 * @a: (type DhLink): a #DhLink.
 * @b: (type DhLink): a #DhLink.
495
 *
496 497 498 499 500 501
 * Compares the links @a and @b. This function is used to determine in which
 * order the links should be displayed.
 *
 * Returns: an integer less than zero if @a should appear before @b; zero if
 * there are no preferences; an integer greater than zero if @b should appear
 * before @a.
502
 */
503 504 505
gint
dh_link_compare (gconstpointer a,
                 gconstpointer b)
506
{
507 508 509 510
        DhLink *la = (DhLink *) a;
        DhLink *lb = (DhLink *) b;
        gint flags_diff;
        gint diff;
511

512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
        g_return_val_if_fail (a != NULL, 0);
        g_return_val_if_fail (b != NULL, 0);

        /* Sort deprecated hits last. */
        flags_diff = ((la->flags & DH_LINK_FLAGS_DEPRECATED) -
                      (lb->flags & DH_LINK_FLAGS_DEPRECATED));
        if (flags_diff != 0)
                return flags_diff;

        /* Collation-based sorting */
        if (G_UNLIKELY (la->name_collation_key == NULL))
                la->name_collation_key = g_utf8_collate_key (la->name, -1);
        if (G_UNLIKELY (lb->name_collation_key == NULL))
                lb->name_collation_key = g_utf8_collate_key (lb->name, -1);

        diff = strcmp (la->name_collation_key,
                       lb->name_collation_key);

530 531
        if (diff != 0)
                return diff;
532

533
        return dh_link_type_compare (la->type, lb->type);
534
}
535

536
/**
537 538
 * dh_link_type_to_string:
 * @link_type: a #DhLinkType.
539
 *
540 541
 * Returns: a string representation of the #DhLinkType, translated in the
 * current language.
542
 */
543
const gchar *
544
dh_link_type_to_string (DhLinkType link_type)
545
{
546
        switch (link_type) {
547 548 549
        case DH_LINK_TYPE_BOOK:
                /* i18n: a documentation book */
                return _("Book");
550

551
        case DH_LINK_TYPE_PAGE:
552 553
                /* i18n: a "page" in a documentation book */
                return _("Page");
554

555
        case DH_LINK_TYPE_KEYWORD:
556 557 558
                /* i18n: a search hit in the documentation, could be a
                 * function, macro, struct, etc */
                return _("Keyword");
559

Richard Hult's avatar
Richard Hult committed
560
        case DH_LINK_TYPE_FUNCTION:
561 562 563 564
                /* i18n: in the programming language context, if you don't
                 * have an ESTABLISHED term for it, leave it
                 * untranslated. */
                return _("Function");
565

566
        case DH_LINK_TYPE_STRUCT:
567 568 569 570
                /* i18n: in the programming language context, if you don't
                 * have an ESTABLISHED term for it, leave it
                 * untranslated. */
                return _("Struct");
571

572
        case DH_LINK_TYPE_MACRO:
573 574 575 576
                /* i18n: in the programming language context, if you don't
                 * have an ESTABLISHED term for it, leave it
                 * untranslated. */
                return _("Macro");
577

578
        case DH_LINK_TYPE_ENUM:
579 580
                /* i18n: in the programming language context, if you don't
                 * have an ESTABLISHED term for it, leave it
Richard Hult's avatar
Richard Hult committed
581
                 * untranslated. */
582 583
                return _("Enum");

584
        case DH_LINK_TYPE_TYPEDEF:
585 586 587 588
                /* i18n: in the programming language context, if you don't
                 * have an ESTABLISHED term for it, leave it
                 * untranslated. */
                return _("Type");
589

590 591 592 593 594
        case DH_LINK_TYPE_PROPERTY:
                /* i18n: in the programming language context, if you don't
                 * have an ESTABLISHED term for it, leave it
                 * untranslated. */
                return _("Property");
595

596 597 598 599 600
        case DH_LINK_TYPE_SIGNAL:
                /* i18n: in the programming language context, if you don't
                 * have an ESTABLISHED term for it, leave it
                 * untranslated. */
                return _("Signal");
601

602
        default:
603
                break;
604 605
        }

606
        g_return_val_if_reached ("");
607
}