Coverage Report

Created: 2026-02-27 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/graph/attributes.c
Line
Count
Source
1
/*
2
   igraph library.
3
   Copyright (C) 2005-2012  Gabor Csardi <csardi.gabor@gmail.com>
4
   334 Harvard street, Cambridge, MA 02139 USA
5
6
   This program is free software; you can redistribute it and/or modify
7
   it under the terms of the GNU General Public License as published by
8
   the Free Software Foundation; either version 2 of the License, or
9
   (at your option) any later version.
10
11
   This program is distributed in the hope that it will be useful,
12
   but WITHOUT ANY WARRANTY; without even the implied warranty of
13
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
   GNU General Public License for more details.
15
16
   You should have received a copy of the GNU General Public License
17
   along with this program; if not, write to the Free Software
18
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
   02110-1301 USA
20
21
*/
22
23
#include "igraph_attributes.h"
24
#include "igraph_memory.h"
25
26
#include "graph/attributes.h"
27
#include "internal/hacks.h" /* strdup */
28
29
#include <string.h>
30
#include <stdarg.h>
31
32
/**
33
 * \section about_attributes
34
 *
35
 * <para>Attributes are numbers, boolean values or strings associated with
36
 * the vertices or edges of a graph, or with the graph itself. E.g. you may
37
 * label vertices with symbolic names or attach numeric weights to the edges
38
 * of a graph. In addition to these three basic types, a custom object
39
 * type is supported as well.</para>
40
 *
41
 * <para>igraph attributes are designed to be flexible and extensible.
42
 * In igraph attributes are implemented via an interface abstraction:
43
 * any type implementing the functions in the interface can be used
44
 * for storing vertex, edge and graph attributes. This means that
45
 * different attribute implementations can be used together with
46
 * igraph. This is reasonable: if igraph is used from Python attributes can be
47
 * of any Python type, from R all R types are allowed. There is also an
48
 * experimental attribute implementation to be used when programming
49
 * in C, but by default it is currently turned off.</para>
50
 *
51
 * <para>First we briefly look over how attribute handlers can be
52
 * implemented. This is not something a user does every day. It is
53
 * rather typically the job of the high level interface writers. (But
54
 * it is possible to write an interface without implementing
55
 * attributes.) Then we show the experimental C attribute handler.</para>
56
 */
57
58
/**
59
 * \section about_attribute_table
60
 * <para>It is possible to attach an attribute handling
61
 * interface to \a igraph. This is simply a table of functions, of
62
 * type \ref igraph_attribute_table_t. These functions are invoked to
63
 * notify the attribute handling code about the structural changes in
64
 * a graph. See the documentation of this type for details.</para>
65
 *
66
 * <para>By default there is no attribute interface attached to \a igraph.
67
 * To attach one, call \ref igraph_set_attribute_table with your new
68
 * table. This is normally done on program startup, and is kept untouched
69
 * for the program's lifetime. It must be done before any graph object
70
 * is created, as graphs created with a given attribute handler
71
 * cannot be manipulated while a different attribute handler is
72
 * active.</para>
73
 */
74
75
/**
76
 * \section about_attribute_record
77
 *
78
 * <para>Functions in the attribute handler interface may refer to
79
 * \em "attribute records" or \em "attribute record lists". An attribute record
80
 * is simply a triplet consisting of an attribute name, an attribute type and
81
 * a vector containing the values of the attribute. Attribute record lists are
82
 * typed containers that contain a sequence of attribute records. Attribute
83
 * record lists own the attribute records that they contain, and similarly,
84
 * attribute records own the vectors contained in them. Destroying an attribute
85
 * record destroys the vector of values inside it, and destroying an attribute
86
 * record list destroys all attribute records in the list.</para>
87
 */
88
89
/**
90
 * \section about_attribute_combination
91
 *
92
 * <para>Several graph operations may collapse multiple vertices or edges into
93
 * a single one. Attribute combination lists are used to indicate to the attribute
94
 * handler how to combine the attributes of the original vertices or edges and
95
 * how to derive the final attribute value that is to be assigned to the collapsed
96
 * vertex or edge. For example, \ref igraph_simplify() removes loops and combines
97
 * multiple edges into a single one; in case of a graph with an edge attribute
98
 * named \c weight the attribute combination list can tell the attribute handler
99
 * whether the weight of a collapsed edge should be the sum, the mean or some other
100
 * function of the weights of the original edges that were collapsed into one.</para>
101
 *
102
 * <para>One attribute combination list may contain several attribute combination
103
 * records, one for each vertex or edge attribute that is to be handled during the
104
 * operation.</para>
105
 */
106
107
static void igraph_i_attribute_record_set_type(
108
    igraph_attribute_record_t *attr, igraph_attribute_type_t type, void *ptr
109
);
110
static void igraph_i_attribute_record_destroy_values(igraph_attribute_record_t *attr);
111
112
/**
113
 * \function igraph_attribute_record_init
114
 * \brief Initializes an attribute record with a given name and type.
115
 *
116
 * \param attr  the attribute record to initialize
117
 * \param name  name of the attribute
118
 * \param type  type of the attribute
119
 * \return Error code:
120
 *         \c IGRAPH_ENOMEM if there is not enough memory.
121
 *
122
 * Time complexity: O(1).
123
 */
124
igraph_error_t igraph_attribute_record_init(
125
    igraph_attribute_record_t *attr, const char *name, igraph_attribute_type_t type
126
530k
) {
127
530k
    attr->name = NULL;
128
530k
    attr->type = IGRAPH_ATTRIBUTE_UNSPECIFIED;
129
530k
    attr->value.as_raw = NULL;
130
530k
    attr->default_value.string = NULL;
131
132
530k
    IGRAPH_CHECK(igraph_attribute_record_set_name(attr, name));
133
530k
    IGRAPH_FINALLY(igraph_free, attr->name);
134
530k
    IGRAPH_CHECK(igraph_attribute_record_set_type(attr, type));
135
530k
    IGRAPH_FINALLY_CLEAN(1);
136
137
530k
    return IGRAPH_SUCCESS;
138
530k
}
139
140
/**
141
 * \function igraph_attribute_record_init_copy
142
 * \brief Initializes an attribute record by copying another record.
143
 *
144
 * </para><para>
145
 * Copies made by this function are deep copies: a full copy of the value
146
 * vector contained in the record is placed in the new record so they become
147
 * independent of each other.
148
 *
149
 * \param to    the attribute record to initialize
150
 * \param from  the attribute record to copy data from
151
 * \return Error code:
152
 *         \c IGRAPH_ENOMEM if there is not enough memory.
153
 *
154
 * Time complexity: operating system dependent, usually O(n), where n is the
155
 * size of the value vector in the attribute record.
156
 */
157
igraph_error_t igraph_attribute_record_init_copy(
158
    igraph_attribute_record_t *to, const igraph_attribute_record_t *from
159
56.8k
) {
160
56.8k
    IGRAPH_CHECK(igraph_attribute_record_init(to, from->name, from->type));
161
162
56.8k
    switch (from->type) {
163
42.6k
        case IGRAPH_ATTRIBUTE_NUMERIC:
164
42.6k
            IGRAPH_CHECK(igraph_vector_update(to->value.as_vector, from->value.as_vector));
165
42.6k
            break;
166
167
42.6k
        case IGRAPH_ATTRIBUTE_STRING:
168
13.6k
            IGRAPH_CHECK(igraph_strvector_update(to->value.as_strvector, from->value.as_strvector));
169
13.6k
            break;
170
171
13.6k
        case IGRAPH_ATTRIBUTE_BOOLEAN:
172
481
            IGRAPH_CHECK(igraph_vector_bool_update(to->value.as_vector_bool, from->value.as_vector_bool));
173
481
            break;
174
175
481
        case IGRAPH_ATTRIBUTE_UNSPECIFIED:
176
0
            break;
177
178
0
        default:
179
0
            break;
180
56.8k
    }
181
182
56.8k
    return IGRAPH_SUCCESS;
183
56.8k
}
184
185
1.05M
static void igraph_i_attribute_record_destroy_values(igraph_attribute_record_t *attr) {
186
1.05M
    IGRAPH_ASSERT(attr != NULL);
187
188
1.05M
    if (attr->value.as_raw) {
189
513k
        switch (attr->type) {
190
226k
            case IGRAPH_ATTRIBUTE_NUMERIC:
191
226k
                igraph_vector_destroy(attr->value.as_vector);
192
226k
                break;
193
194
279k
            case IGRAPH_ATTRIBUTE_STRING:
195
279k
                igraph_strvector_destroy(attr->value.as_strvector);
196
279k
                break;
197
198
7.70k
            case IGRAPH_ATTRIBUTE_BOOLEAN:
199
7.70k
                igraph_vector_bool_destroy(attr->value.as_vector_bool);
200
7.70k
                break;
201
202
0
            default:
203
0
                break;
204
513k
        }
205
206
513k
        igraph_free(attr->value.as_raw);
207
513k
        attr->value.as_raw = NULL;
208
513k
    }
209
210
1.05M
    switch (attr->type) {
211
226k
        case IGRAPH_ATTRIBUTE_NUMERIC:
212
226k
            attr->default_value.numeric = 0;
213
226k
            break;
214
215
279k
        case IGRAPH_ATTRIBUTE_STRING:
216
279k
            if (attr->default_value.string) {
217
1.10k
                igraph_free(attr->default_value.string);
218
1.10k
                attr->default_value.string = NULL;
219
1.10k
            }
220
279k
            break;
221
222
7.70k
        case IGRAPH_ATTRIBUTE_BOOLEAN:
223
7.70k
            attr->default_value.boolean = 0;
224
7.70k
            break;
225
226
537k
        default:
227
537k
            break;
228
1.05M
    }
229
230
1.05M
    attr->type = IGRAPH_ATTRIBUTE_UNSPECIFIED;
231
1.05M
}
232
233
/**
234
 * \function igraph_attribute_record_destroy
235
 * \brief Destroys an attribute record.
236
 *
237
 * \param attr  the previously initialized attribute record to destroy.
238
 *
239
 * Time complexity: operating system dependent.
240
 */
241
537k
void igraph_attribute_record_destroy(igraph_attribute_record_t *attr) {
242
537k
    igraph_i_attribute_record_destroy_values(attr);
243
244
537k
    if (attr->name) {
245
530k
        igraph_free(attr->name);
246
530k
        attr->name = NULL;
247
530k
    }
248
537k
}
249
250
/**
251
 * \function igraph_attribute_record_check_type
252
 * \brief Checks whether the type of the attribute record is equal to an expected type.
253
 *
254
 * \param attr  the attribute record to test
255
 * \param type  the expected type of the attribute record
256
 * \return Error code:
257
 *         \c IGRAPH_EINVAL if the type of the attribute record is not equal to
258
 *         the expected type
259
 *
260
 * Time complexity: O(1).
261
 */
262
igraph_error_t igraph_attribute_record_check_type(
263
    const igraph_attribute_record_t *attr, igraph_attribute_type_t type
264
66.5M
) {
265
66.5M
    if (type != attr->type) {
266
1
        switch (type) {
267
0
            case IGRAPH_ATTRIBUTE_STRING:
268
0
                IGRAPH_ERROR("String attribute expected.", IGRAPH_EINVAL);
269
0
                break;
270
0
            case IGRAPH_ATTRIBUTE_NUMERIC:
271
0
                IGRAPH_ERROR("Numeric attribute expected.", IGRAPH_EINVAL);
272
0
                break;
273
1
            case IGRAPH_ATTRIBUTE_BOOLEAN:
274
1
                IGRAPH_ERROR("Boolean attribute expected.", IGRAPH_EINVAL);
275
0
                break;
276
0
            case IGRAPH_ATTRIBUTE_OBJECT:
277
0
                IGRAPH_ERROR("Object attribute expected.", IGRAPH_EINVAL);
278
0
                break;
279
0
            default:
280
0
                IGRAPH_ERROR("Attribute with unknown type expected.", IGRAPH_EINVAL);
281
0
                break;
282
1
        }
283
1
    }
284
285
66.5M
    return IGRAPH_SUCCESS;
286
66.5M
}
287
288
/**
289
 * \function igraph_attribute_record_size
290
 * \brief Returns the size of the value vector in an attribute record.
291
 *
292
 * \param attr      the attribute record to query
293
 * \return the number of elements in the value vector of the attribute record
294
 */
295
2.58M
igraph_int_t igraph_attribute_record_size(const igraph_attribute_record_t *attr) {
296
2.58M
    IGRAPH_ASSERT(attr != NULL);
297
298
2.58M
    switch (attr->type) {
299
351k
        case IGRAPH_ATTRIBUTE_NUMERIC:
300
351k
            return igraph_vector_size(attr->value.as_vector);
301
302
2.20M
        case IGRAPH_ATTRIBUTE_STRING:
303
2.20M
            return igraph_strvector_size(attr->value.as_strvector);
304
305
20.0k
        case IGRAPH_ATTRIBUTE_BOOLEAN:
306
20.0k
            return igraph_vector_bool_size(attr->value.as_vector_bool);
307
308
11.9k
        case IGRAPH_ATTRIBUTE_UNSPECIFIED:
309
11.9k
            return 0;
310
311
0
        default:
312
0
            IGRAPH_ERRORF("Unsupported attribute type: %d", IGRAPH_EINVAL, (int) attr->type);
313
2.58M
    }
314
2.58M
}
315
316
/**
317
 * \function igraph_attribute_record_resize
318
 * \brief Resizes the value vector in an attribute record.
319
 *
320
 * </para><para>When the value vector is shorter than the desired length, it
321
 * will be expanded with \c IGRAPH_NAN for numeric vectors, \c false for Boolean
322
 * vectors and empty strings for string vectors.
323
 *
324
 * \param attr      the attribute record to update
325
 * \param new_size  the new size of the value vector
326
 * \return Error code:
327
 *         \c IGRAPH_ENOMEM if there is not enough memory.
328
 *         \c IGRAPH_EINVAL if the type of the attribute record is not specified yet.
329
 */
330
igraph_error_t igraph_attribute_record_resize(
331
    igraph_attribute_record_t *attr, igraph_int_t new_size
332
2.40M
) {
333
2.40M
    igraph_int_t i;
334
2.40M
    igraph_vector_t *vec;
335
2.40M
    igraph_vector_bool_t *log;
336
2.40M
    igraph_strvector_t *str;
337
338
2.40M
    IGRAPH_ASSERT(attr != NULL);
339
340
2.40M
    switch (attr->type) {
341
342
320k
        case IGRAPH_ATTRIBUTE_NUMERIC:
343
320k
            vec = attr->value.as_vector;
344
320k
            i = igraph_vector_size(vec);
345
320k
            IGRAPH_CHECK(igraph_vector_resize(vec, new_size));
346
32.2M
            while (i < new_size) {
347
31.9M
                VECTOR(*vec)[i++] = attr->default_value.numeric;
348
31.9M
            }
349
320k
            break;
350
351
16.4k
        case IGRAPH_ATTRIBUTE_BOOLEAN:
352
16.4k
            log = attr->value.as_vector_bool;
353
16.4k
            i = igraph_vector_bool_size(log);
354
16.4k
            IGRAPH_CHECK(igraph_vector_bool_resize(log, new_size));
355
11.5M
            while (i < new_size) {
356
11.4M
                VECTOR(*log)[i++] = attr->default_value.boolean;
357
11.4M
            }
358
16.4k
            break;
359
360
2.07M
        case IGRAPH_ATTRIBUTE_STRING:
361
2.07M
            str = attr->value.as_strvector;
362
2.07M
            if (attr->default_value.string == 0 || (*attr->default_value.string == 0)) {
363
2.06M
                IGRAPH_CHECK(igraph_strvector_resize(str, new_size));
364
2.06M
            } else {
365
1.14k
                i = igraph_strvector_size(str);
366
1.14k
                IGRAPH_CHECK(igraph_strvector_resize(str, new_size));
367
1.80M
                while (i < new_size) {
368
1.80M
                    IGRAPH_CHECK(igraph_strvector_set(str, i++, attr->default_value.string));
369
1.80M
                }
370
1.14k
            }
371
2.07M
            break;
372
373
2.07M
        case IGRAPH_ATTRIBUTE_UNSPECIFIED:
374
0
            IGRAPH_ERROR("Attribute record has no type yet.", IGRAPH_EINVAL);
375
0
            break;
376
377
0
        default:
378
0
            IGRAPH_ERRORF("Unsupported attribute type: %d", IGRAPH_EINVAL, (int) attr->type);
379
2.40M
    }
380
381
2.40M
    return IGRAPH_SUCCESS;
382
2.40M
}
383
384
/**
385
 * \function igraph_attribute_record_set_default_numeric
386
 * \brief Sets the default value of the attribute to the given number.
387
 *
388
 * </para><para>
389
 * This function must be called for numeric attribute records only. When not
390
 * specified, the default value of numeric attributes is NaN.
391
 *
392
 * \param attr   the attribute record to update
393
 * \param value  the new default value
394
 * \return Error code:
395
 *         \c IGRAPH_EINVAL if the attribute record has a non-numeric type
396
 */
397
igraph_error_t igraph_attribute_record_set_default_numeric(
398
    igraph_attribute_record_t *attr, igraph_real_t value
399
229k
) {
400
229k
    if (attr->type != IGRAPH_ATTRIBUTE_NUMERIC) {
401
0
        return IGRAPH_EINVAL;
402
0
    }
403
404
229k
    attr->default_value.numeric = value;
405
229k
    return IGRAPH_SUCCESS;
406
229k
}
407
408
/**
409
 * \function igraph_attribute_record_set_default_boolean
410
 * \brief Sets the default value of the attribute to the given logical value.
411
 *
412
 * </para><para>
413
 * This function must be called for Boolean attribute records only. When not
414
 * specified, the default value of Boolean attributes is \c false.
415
 *
416
 * \param attr   the attribute record to update
417
 * \param value  the new default value
418
 * \return Error code:
419
 *         \c IGRAPH_EINVAL if the attribute record is not of Boolean type
420
 */
421
IGRAPH_EXPORT igraph_error_t igraph_attribute_record_set_default_boolean(
422
    igraph_attribute_record_t *attr, igraph_bool_t value
423
3.65k
) {
424
3.65k
    if (attr->type != IGRAPH_ATTRIBUTE_BOOLEAN) {
425
0
        return IGRAPH_EINVAL;
426
0
    }
427
428
3.65k
    attr->default_value.boolean = value;
429
3.65k
    return IGRAPH_SUCCESS;
430
3.65k
}
431
432
/**
433
 * \function igraph_attribute_record_set_default_string
434
 * \brief Sets the default value of the attribute to the given string.
435
 *
436
 * </para><para>
437
 * This function must be called for string attribute records only. When not
438
 * specified, the default value of string attributes is an empty string.
439
 *
440
 * \param attr   the attribute record to update
441
 * \param value  the new default value. \c NULL means an empty string.
442
 *
443
 * \return Error code:
444
 *         \c IGRAPH_ENOMEM if there is not enough memory
445
 *         \c IGRAPH_EINVAL if the attribute record is not of string type
446
 */
447
IGRAPH_EXPORT igraph_error_t igraph_attribute_record_set_default_string(
448
    igraph_attribute_record_t *attr, const char* value
449
6.92k
) {
450
6.92k
    char* copy;
451
452
6.92k
    if (attr->type != IGRAPH_ATTRIBUTE_STRING) {
453
0
        return IGRAPH_EINVAL;
454
0
    }
455
456
6.92k
    if (value && (*value != 0)) {
457
1.56k
        copy = strdup(value);
458
1.56k
        IGRAPH_CHECK_OOM(copy, "Insufficient memory to duplicate default value.");
459
5.36k
    } else {
460
5.36k
        copy = NULL;
461
5.36k
    }
462
463
6.92k
    if (attr->default_value.string) {
464
454
        igraph_free(attr->default_value.string);
465
454
    }
466
6.92k
    attr->default_value.string = copy;
467
468
6.92k
    return IGRAPH_SUCCESS;
469
6.92k
}
470
471
472
/**
473
 * \function igraph_attribute_record_set_name
474
 * \brief Sets the attribute name in an attribute record.
475
 *
476
 * \param attr  the attribute record to update
477
 * \param name  the new name
478
 * \return Error code:
479
 *         \c IGRAPH_ENOMEM if there is not enough memory.
480
 */
481
igraph_error_t igraph_attribute_record_set_name(
482
    igraph_attribute_record_t *attr, const char *name
483
759k
) {
484
759k
    char *new_name;
485
486
759k
    IGRAPH_ASSERT(attr != NULL);
487
488
759k
    if (name != NULL) {
489
530k
        new_name = strdup(name);
490
530k
        IGRAPH_CHECK_OOM(new_name, "Insufficient memory for allocating attribute name.");
491
530k
    } else {
492
229k
        new_name = NULL;
493
229k
    }
494
495
759k
    if (attr->name) {
496
0
        igraph_free(attr->name);
497
0
    }
498
499
759k
    attr->name = new_name;
500
501
759k
    return IGRAPH_SUCCESS;
502
759k
}
503
504
static void igraph_i_attribute_record_set_type(
505
    igraph_attribute_record_t *attr, igraph_attribute_type_t type, void *ptr
506
513k
) {
507
513k
    bool type_changed = attr->type != type;
508
509
513k
    if (type_changed || attr->value.as_raw != ptr) {
510
513k
        igraph_i_attribute_record_destroy_values(attr);
511
513k
        attr->type = type;
512
513k
        attr->value.as_raw = ptr;
513
513k
    }
514
515
513k
    if (type_changed && type == IGRAPH_ATTRIBUTE_NUMERIC) {
516
226k
        IGRAPH_ASSERT(
517
226k
            igraph_attribute_record_set_default_numeric(attr, IGRAPH_NAN) == IGRAPH_SUCCESS
518
226k
        );
519
226k
    }
520
513k
}
521
522
/**
523
 * \function igraph_attribute_record_set_type
524
 * \brief Sets the type of an attribute record.
525
 *
526
 * </para><para>
527
 * When the new type being set is different from the old type, any values already
528
 * stored in the attribute record will be destroyed and a new, empty attribute
529
 * value vector will be allocated. When the new type is the same as the old
530
 * type, this function is a no-op.
531
 *
532
 * \param attr  the attribute record to update
533
 * \param type  the new type
534
 * \return Error code:
535
 *         \c IGRAPH_ENOMEM if there is not enough memory.
536
 */
537
igraph_error_t igraph_attribute_record_set_type(
538
    igraph_attribute_record_t *attr, igraph_attribute_type_t type
539
760k
) {
540
760k
    void *ptr;
541
542
760k
    IGRAPH_ASSERT(attr != NULL);
543
544
760k
    if (attr->type != type) {
545
513k
        switch (type) {
546
226k
            case IGRAPH_ATTRIBUTE_NUMERIC: {
547
226k
                igraph_vector_t *vec = IGRAPH_CALLOC(1, igraph_vector_t);
548
226k
                IGRAPH_CHECK_OOM(vec, "Insufficient memory for attribute record.");
549
226k
                IGRAPH_FINALLY(igraph_free, vec);
550
226k
                IGRAPH_VECTOR_INIT_FINALLY(vec, 0);
551
226k
                ptr = vec;
552
226k
            }
553
0
            break;
554
555
279k
            case IGRAPH_ATTRIBUTE_STRING: {
556
279k
                igraph_strvector_t *strvec = IGRAPH_CALLOC(1, igraph_strvector_t);
557
279k
                IGRAPH_CHECK_OOM(strvec, "Insufficient memory for attribute record.");
558
279k
                IGRAPH_FINALLY(igraph_free, strvec);
559
279k
                IGRAPH_STRVECTOR_INIT_FINALLY(strvec, 0);
560
279k
                ptr = strvec;
561
279k
            }
562
0
            break;
563
564
7.70k
            case IGRAPH_ATTRIBUTE_BOOLEAN: {
565
7.70k
                igraph_vector_bool_t *boolvec = IGRAPH_CALLOC(1, igraph_vector_bool_t);
566
7.70k
                IGRAPH_CHECK_OOM(boolvec, "Insufficient memory for attribute record.");
567
7.70k
                IGRAPH_FINALLY(igraph_free, boolvec);
568
7.70k
                IGRAPH_VECTOR_BOOL_INIT_FINALLY(boolvec, 0);
569
7.70k
                ptr = boolvec;
570
7.70k
            }
571
0
            break;
572
573
0
            default:
574
0
                IGRAPH_FATALF("Unsupported attribute type: %d.", (int) type);
575
513k
        }
576
577
513k
        igraph_i_attribute_record_set_type(attr, type, ptr);
578
513k
        IGRAPH_FINALLY_CLEAN(2);
579
513k
    }
580
581
760k
    return IGRAPH_SUCCESS;
582
760k
}
583
584
#define ATTRIBUTE_RECORD_LIST
585
#define BASE_ATTRIBUTE_RECORD
586
#define CUSTOM_INIT_DESTROY
587
#include "igraph_pmt.h"
588
#include "core/typed_list.pmt"
589
#include "igraph_pmt_off.h"
590
#undef CUSTOM_INIT_DESTROY
591
#undef BASE_ATTRIBUTE_RECORD
592
#undef ATTRIBUTE_RECORD_LIST
593
594
static igraph_error_t igraph_i_attribute_record_list_init_item(
595
    const igraph_attribute_record_list_t* list, igraph_attribute_record_t* item
596
229k
) {
597
229k
    IGRAPH_UNUSED(list);
598
229k
    return igraph_attribute_record_init(item, NULL, IGRAPH_ATTRIBUTE_UNSPECIFIED);
599
229k
}
600
601
static igraph_error_t igraph_i_attribute_record_list_copy_item(
602
    igraph_attribute_record_t* dest, const igraph_attribute_record_t* source
603
56.8k
) {
604
56.8k
    return igraph_attribute_record_init_copy(dest, source);
605
56.8k
}
606
607
513k
static void igraph_i_attribute_record_list_destroy_item(igraph_attribute_record_t* item) {
608
513k
    igraph_attribute_record_destroy(item);
609
513k
}
610
611
/* Should you ever want to have a thread-local attribute handler table, prepend
612
 * IGRAPH_THREAD_LOCAL to the following declaration and #include "config.h". */
613
igraph_attribute_table_t *igraph_i_attribute_table = NULL;
614
615
igraph_error_t igraph_i_attribute_init(
616
    igraph_t *graph, const igraph_attribute_record_list_t *attr
617
410k
) {
618
410k
    graph->attr = NULL;
619
410k
    if (igraph_i_attribute_table) {
620
109k
        IGRAPH_CHECK(igraph_i_attribute_table->init(graph, attr));
621
109k
        if (graph->attr == NULL) {
622
0
            IGRAPH_ERROR("Attribute handler did not initialize attr pointer", IGRAPH_FAILURE);
623
0
        }
624
109k
    }
625
410k
    return IGRAPH_SUCCESS;
626
410k
}
627
628
640k
void igraph_i_attribute_destroy(igraph_t *graph) {
629
640k
    if (graph->attr && igraph_i_attribute_table) {
630
130k
        igraph_i_attribute_table->destroy(graph);
631
130k
    }
632
640k
    graph->attr = NULL;
633
640k
}
634
635
igraph_error_t igraph_i_attribute_copy(igraph_t *to, const igraph_t *from, igraph_bool_t ga,
636
167k
                            igraph_bool_t va, igraph_bool_t ea) {
637
167k
    igraph_i_attribute_destroy(to);
638
167k
    if (from->attr && igraph_i_attribute_table) {
639
20.9k
        IGRAPH_CHECK(igraph_i_attribute_table->copy(to, from, ga, va, ea));
640
20.9k
        if (to->attr == NULL) {
641
0
            IGRAPH_ERROR("Attribute handler did not initialize attr pointer", IGRAPH_FAILURE);
642
0
        }
643
20.9k
    }
644
167k
    return IGRAPH_SUCCESS;
645
167k
}
646
647
igraph_error_t igraph_i_attribute_add_vertices(
648
    igraph_t *graph, igraph_int_t nv, const igraph_attribute_record_list_t *attr
649
167k
) {
650
167k
    if (igraph_i_attribute_table) {
651
167k
        return igraph_i_attribute_table->add_vertices(graph, nv, attr);
652
167k
    } else {
653
0
        return IGRAPH_SUCCESS;
654
0
    }
655
167k
}
656
657
igraph_error_t igraph_i_attribute_permute_vertices(const igraph_t *graph,
658
                                        igraph_t *newgraph,
659
16.9k
                                        const igraph_vector_int_t *idx) {
660
    /* graph and newgraph may be the same, in which case we need to support
661
     * in-place operations. If they are _not_ the same, it is assumed that the
662
     * new graph has no vertex attributes yet */
663
16.9k
    if (igraph_i_attribute_table) {
664
0
        return igraph_i_attribute_table->permute_vertices(graph, newgraph, idx);
665
16.9k
    } else {
666
16.9k
        return IGRAPH_SUCCESS;
667
16.9k
    }
668
16.9k
}
669
670
igraph_error_t igraph_i_attribute_combine_vertices(const igraph_t *graph,
671
                                        igraph_t *newgraph,
672
                                        const igraph_vector_int_list_t *merges,
673
3.58k
                                        const igraph_attribute_combination_t *comb) {
674
    /* It is assumed that the two graphs are not the same and that the new
675
     * graph has no vertex attributes yet. We cannot assert the latter but we
676
     * can assert the former */
677
3.58k
    IGRAPH_ASSERT(graph != newgraph);
678
3.58k
    if (igraph_i_attribute_table) {
679
3.58k
        return igraph_i_attribute_table->combine_vertices(graph, newgraph,
680
3.58k
                merges,
681
3.58k
                comb);
682
3.58k
    } else {
683
0
        return IGRAPH_SUCCESS;
684
0
    }
685
3.58k
}
686
687
igraph_error_t igraph_i_attribute_add_edges(
688
    igraph_t *graph, const igraph_vector_int_t *edges,
689
    const igraph_attribute_record_list_t *attr
690
92.9k
) {
691
92.9k
    if (igraph_i_attribute_table) {
692
92.9k
        return igraph_i_attribute_table->add_edges(graph, edges, attr);
693
92.9k
    } else {
694
0
        return IGRAPH_SUCCESS;
695
0
    }
696
92.9k
}
697
698
igraph_error_t igraph_i_attribute_permute_edges(const igraph_t *graph,
699
                                     igraph_t *newgraph,
700
27.8k
                                     const igraph_vector_int_t *idx) {
701
    /* graph and newgraph may be the same, in which case we need to support
702
     * in-place operations. If they are _not_ the same, it is assumed that the
703
     * new graph has no edge attributes yet */
704
27.8k
    if (igraph_i_attribute_table) {
705
0
        return igraph_i_attribute_table->permute_edges(graph, newgraph, idx);
706
27.8k
    } else {
707
27.8k
        return IGRAPH_SUCCESS;
708
27.8k
    }
709
27.8k
}
710
711
igraph_error_t igraph_i_attribute_combine_edges(const igraph_t *graph,
712
                                     igraph_t *newgraph,
713
                                     const igraph_vector_int_list_t *merges,
714
10.2k
                                     const igraph_attribute_combination_t *comb) {
715
    /* It is assumed that the two graphs are not the same and that the new
716
     * graph has no eedge attributes yet. We cannot assert the latter but we
717
     * can assert the former */
718
10.2k
    IGRAPH_ASSERT(graph != newgraph);
719
10.2k
    if (igraph_i_attribute_table) {
720
10.2k
        return igraph_i_attribute_table->combine_edges(graph, newgraph,
721
10.2k
                merges,
722
10.2k
                comb);
723
10.2k
    } else {
724
0
        return IGRAPH_SUCCESS;
725
0
    }
726
10.2k
}
727
728
igraph_error_t igraph_i_attribute_get_info(const igraph_t *graph,
729
                                igraph_strvector_t *gnames,
730
                                igraph_vector_int_t *gtypes,
731
                                igraph_strvector_t *vnames,
732
                                igraph_vector_int_t *vtypes,
733
                                igraph_strvector_t *enames,
734
31.2k
                                igraph_vector_int_t *etypes) {
735
31.2k
    if (igraph_i_attribute_table) {
736
31.2k
        return igraph_i_attribute_table->get_info(graph, gnames, gtypes,
737
31.2k
                vnames, vtypes,
738
31.2k
                enames, etypes);
739
31.2k
    } else {
740
0
        return IGRAPH_SUCCESS;
741
0
    }
742
31.2k
}
743
744
igraph_bool_t igraph_i_attribute_has_attr(const igraph_t *graph,
745
        igraph_attribute_elemtype_t type,
746
714k
        const char *name) {
747
714k
    if (igraph_i_attribute_table) {
748
714k
        return igraph_i_attribute_table->has_attr(graph, type, name);
749
714k
    } else {
750
0
        return IGRAPH_SUCCESS;
751
0
    }
752
714k
}
753
754
igraph_error_t igraph_i_attribute_get_type(const igraph_t *graph,
755
                               igraph_attribute_type_t *type,
756
                               igraph_attribute_elemtype_t elemtype,
757
14.4k
                               const char *name) {
758
14.4k
    if (igraph_i_attribute_table) {
759
14.4k
        return igraph_i_attribute_table->get_type(graph, type, elemtype, name);
760
14.4k
    } else {
761
0
        return IGRAPH_SUCCESS;
762
0
    }
763
764
14.4k
}
765
766
igraph_error_t igraph_i_attribute_get_numeric_graph_attr(const igraph_t *graph,
767
        const char *name,
768
18.6k
        igraph_vector_t *value) {
769
18.6k
    igraph_vector_clear(value);
770
18.6k
    if (igraph_i_attribute_table) {
771
18.6k
        return igraph_i_attribute_table->get_numeric_graph_attr(graph, name, value);
772
18.6k
    } else {
773
0
        return IGRAPH_SUCCESS;
774
0
    }
775
18.6k
}
776
777
igraph_error_t igraph_i_attribute_get_numeric_vertex_attr(const igraph_t *graph,
778
        const char *name,
779
        igraph_vs_t vs,
780
16.9M
        igraph_vector_t *value) {
781
16.9M
    igraph_vector_clear(value);
782
16.9M
    if (igraph_i_attribute_table) {
783
16.9M
        return igraph_i_attribute_table->get_numeric_vertex_attr(graph, name, vs, value);
784
16.9M
    } else {
785
0
        return IGRAPH_SUCCESS;
786
0
    }
787
16.9M
}
788
789
igraph_error_t igraph_i_attribute_get_numeric_edge_attr(const igraph_t *graph,
790
        const char *name,
791
        igraph_es_t es,
792
11.0M
        igraph_vector_t *value) {
793
11.0M
    igraph_vector_clear(value);
794
11.0M
    if (igraph_i_attribute_table) {
795
11.0M
        return igraph_i_attribute_table->get_numeric_edge_attr(graph, name, es, value);
796
11.0M
    } else {
797
0
        return IGRAPH_SUCCESS;
798
0
    }
799
11.0M
}
800
801
igraph_error_t igraph_i_attribute_get_string_graph_attr(const igraph_t *graph,
802
        const char *name,
803
15.2k
        igraph_strvector_t *value) {
804
15.2k
    igraph_strvector_clear(value);
805
15.2k
    if (igraph_i_attribute_table) {
806
15.2k
        return igraph_i_attribute_table->get_string_graph_attr(graph, name, value);
807
15.2k
    } else {
808
0
        return IGRAPH_SUCCESS;
809
0
    }
810
15.2k
}
811
812
igraph_error_t igraph_i_attribute_get_string_vertex_attr(const igraph_t *graph,
813
        const char *name,
814
        igraph_vs_t vs,
815
13.9M
        igraph_strvector_t *value) {
816
13.9M
    igraph_strvector_clear(value);
817
13.9M
    if (igraph_i_attribute_table) {
818
13.9M
        return igraph_i_attribute_table->get_string_vertex_attr(graph, name, vs, value);
819
13.9M
    } else {
820
0
        return IGRAPH_SUCCESS;
821
0
    }
822
13.9M
}
823
824
igraph_error_t igraph_i_attribute_get_string_edge_attr(const igraph_t *graph,
825
        const char *name,
826
        igraph_es_t es,
827
2.37M
        igraph_strvector_t *value) {
828
2.37M
    igraph_strvector_clear(value);
829
2.37M
    if (igraph_i_attribute_table) {
830
2.37M
        return igraph_i_attribute_table->get_string_edge_attr(graph, name, es, value);
831
2.37M
    } else {
832
0
        return IGRAPH_SUCCESS;
833
0
    }
834
2.37M
}
835
836
igraph_error_t igraph_i_attribute_get_bool_graph_attr(const igraph_t *graph,
837
        const char *name,
838
659
        igraph_vector_bool_t *value) {
839
659
    igraph_vector_bool_clear(value);
840
659
    if (igraph_i_attribute_table) {
841
659
        return igraph_i_attribute_table->get_bool_graph_attr(graph, name, value);
842
659
    } else {
843
0
        return IGRAPH_SUCCESS;
844
0
    }
845
659
}
846
847
igraph_error_t igraph_i_attribute_get_bool_vertex_attr(const igraph_t *graph,
848
        const char *name,
849
        igraph_vs_t vs,
850
22.1M
        igraph_vector_bool_t *value) {
851
22.1M
    igraph_vector_bool_clear(value);
852
22.1M
    if (igraph_i_attribute_table) {
853
22.1M
        return igraph_i_attribute_table->get_bool_vertex_attr(graph, name, vs, value);
854
22.1M
    } else {
855
0
        return IGRAPH_SUCCESS;
856
0
    }
857
22.1M
}
858
859
igraph_error_t igraph_i_attribute_get_bool_edge_attr(const igraph_t *graph,
860
        const char *name,
861
        igraph_es_t es,
862
21.3k
        igraph_vector_bool_t *value) {
863
21.3k
    igraph_vector_bool_clear(value);
864
21.3k
    if (igraph_i_attribute_table) {
865
21.3k
        return igraph_i_attribute_table->get_bool_edge_attr(graph, name, es, value);
866
21.3k
    } else {
867
0
        return IGRAPH_SUCCESS;
868
0
    }
869
21.3k
}
870
871
/**
872
 * \function igraph_set_attribute_table
873
 * \brief Attach an attribute table.
874
 *
875
 * This function attaches attribute handling code to the igraph library.
876
 * Note that the attribute handler table is \em not thread-local even if
877
 * igraph is compiled in thread-local mode. In the vast majority of cases,
878
 * this is not a significant restriction.
879
 *
880
 * </para><para>
881
 * Attribute handlers are normally attached on program startup, and are
882
 * left active for the program's lifetime. This is because a graph object
883
 * created with a given attribute handler must not be manipulated while
884
 * a different attribute handler is active.
885
 *
886
 * \param table Pointer to an \ref igraph_attribute_table_t object
887
 *    containing the functions for attribute manipulation. Supply \c
888
 *    NULL here if you don't want attributes.
889
 * \return Pointer to the old attribute handling table.
890
 *
891
 * Time complexity: O(1).
892
 */
893
894
igraph_attribute_table_t *
895
67.4k
igraph_set_attribute_table(const igraph_attribute_table_t * table) {
896
67.4k
    igraph_attribute_table_t *old = igraph_i_attribute_table;
897
67.4k
    igraph_i_attribute_table = (igraph_attribute_table_t*) table;
898
67.4k
    return old;
899
67.4k
}
900
901
13.8k
igraph_bool_t igraph_has_attribute_table(void) {
902
13.8k
    return igraph_i_attribute_table != NULL;
903
13.8k
}
904
905
906
/**
907
 * \function igraph_attribute_combination_init
908
 * \brief Initialize attribute combination list.
909
 *
910
 * \param comb The uninitialized attribute combination list.
911
 * \return Error code.
912
 *
913
 * Time complexity: O(1)
914
 */
915
5.90k
igraph_error_t igraph_attribute_combination_init(igraph_attribute_combination_t *comb) {
916
5.90k
    IGRAPH_CHECK(igraph_vector_ptr_init(&comb->list, 0));
917
5.90k
    return IGRAPH_SUCCESS;
918
5.90k
}
919
920
/**
921
 * \function igraph_attribute_combination_destroy
922
 * \brief Destroy attribute combination list.
923
 *
924
 * \param comb The attribute combination list.
925
 *
926
 * Time complexity: O(n), where n is the number of records in the
927
                    attribute combination list.
928
 */
929
5.90k
void igraph_attribute_combination_destroy(igraph_attribute_combination_t *comb) {
930
5.90k
    igraph_int_t i, n = igraph_vector_ptr_size(&comb->list);
931
15.3k
    for (i = 0; i < n; i++) {
932
9.49k
        igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[i];
933
9.49k
        if (rec->name) {
934
9.49k
            IGRAPH_FREE(rec->name);
935
9.49k
        }
936
9.49k
        IGRAPH_FREE(rec);
937
9.49k
    }
938
5.90k
    igraph_vector_ptr_destroy(&comb->list);
939
5.90k
}
940
941
/**
942
 * \function igraph_attribute_combination_add
943
 * \brief Add combination record to attribute combination list.
944
 *
945
 * \param comb The attribute combination list.
946
 * \param name The name of the attribute. If the name already exists
947
 *             the attribute combination record will be replaced.
948
 *             Use NULL to add a default combination record for all
949
 *             atributes not in the list.
950
 * \param type The type of the attribute combination. See \ref
951
 *             igraph_attribute_combination_type_t for the options.
952
 * \param func Function to be used if \p type is
953
 *             \c IGRAPH_ATTRIBUTE_COMBINE_FUNCTION. This function is called
954
 *             by the concrete attribute handler attached to igraph, and its
955
 *             calling signature depends completely on the attribute handler.
956
 *             For instance, if you are using attributes from C and you have
957
 *             attached the C attribute handler, you need to follow the
958
 *             documentation of the <link linkend="c-attribute-combination-functions">C attribute handler</link>
959
 *             for more details.
960
 * \return Error code.
961
 *
962
 * Time complexity: O(n), where n is the number of current attribute
963
 *                  combinations.
964
 */
965
igraph_error_t igraph_attribute_combination_add(igraph_attribute_combination_t *comb,
966
                                     const char *name,
967
                                     igraph_attribute_combination_type_t type,
968
9.49k
                                     igraph_function_pointer_t func) {
969
9.49k
    igraph_int_t i, n = igraph_vector_ptr_size(&comb->list);
970
971
    /* Search, in case it is already there */
972
13.0k
    for (i = 0; i < n; i++) {
973
3.58k
        igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i];
974
3.58k
        const char *n = r->name;
975
3.58k
        if ( (!name && !n) ||
976
3.58k
             (name && n && !strcmp(n, name)) ) {
977
0
            r->type = type;
978
0
            r->func = func;
979
0
            break;
980
0
        }
981
3.58k
    }
982
983
9.49k
    if (i == n) {
984
        /* This is a new attribute name */
985
9.49k
        igraph_attribute_combination_record_t *rec =
986
9.49k
            IGRAPH_CALLOC(1, igraph_attribute_combination_record_t);
987
9.49k
        if (! rec) {
988
0
            IGRAPH_ERROR("Cannot create attribute combination data.",
989
0
                         IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */
990
0
        }
991
9.49k
        IGRAPH_FINALLY(igraph_free, rec);
992
9.49k
        if (! name) {
993
0
            rec->name = NULL;
994
9.49k
        } else {
995
9.49k
            rec->name = strdup(name);
996
9.49k
            if (! rec->name) {
997
0
                IGRAPH_ERROR("Cannot create attribute combination data.",
998
0
                             IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */
999
0
            }
1000
9.49k
        }
1001
9.49k
        IGRAPH_FINALLY(igraph_free, (char *) rec->name); /* free() is safe on NULL */
1002
9.49k
        rec->type = type;
1003
9.49k
        rec->func = func;
1004
1005
9.49k
        IGRAPH_CHECK(igraph_vector_ptr_push_back(&comb->list, rec));
1006
9.49k
        IGRAPH_FINALLY_CLEAN(2); /* ownership of 'rec' transferred to 'comb->list' */
1007
1008
9.49k
    }
1009
1010
9.49k
    return IGRAPH_SUCCESS;
1011
9.49k
}
1012
1013
/**
1014
 * \function igraph_attribute_combination_remove
1015
 * \brief Remove a record from an attribute combination list.
1016
 *
1017
 * \param comb The attribute combination list.
1018
 * \param name The attribute name of the attribute combination record
1019
 *             to remove. It will be ignored if the named attribute
1020
 *             does not exist. It can be NULL to remove the default
1021
 *             combination record.
1022
 * \return Error code. This currently always returns IGRAPH_SUCCESS.
1023
 *
1024
 * Time complexity: O(n), where n is the number of records in the attribute
1025
                    combination list.
1026
 */
1027
igraph_error_t igraph_attribute_combination_remove(igraph_attribute_combination_t *comb,
1028
0
                                        const char *name) {
1029
0
    igraph_int_t i, n = igraph_vector_ptr_size(&comb->list);
1030
1031
    /* Search, in case it is already there */
1032
0
    for (i = 0; i < n; i++) {
1033
0
        igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i];
1034
0
        const char *n = r->name;
1035
0
        if ( (!name && !n) ||
1036
0
             (name && n && !strcmp(n, name)) ) {
1037
0
            break;
1038
0
        }
1039
0
    }
1040
1041
0
    if (i != n) {
1042
0
        igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i];
1043
0
        if (r->name) {
1044
0
            IGRAPH_FREE(r->name);
1045
0
        }
1046
0
        IGRAPH_FREE(r);
1047
0
        igraph_vector_ptr_remove(&comb->list, i);
1048
0
    } else {
1049
        /* It is not there, we don't do anything */
1050
0
    }
1051
1052
0
    return IGRAPH_SUCCESS;
1053
0
}
1054
1055
igraph_error_t igraph_attribute_combination_query(const igraph_attribute_combination_t *comb,
1056
                                       const char *name,
1057
                                       igraph_attribute_combination_type_t *type,
1058
13.8k
                                       igraph_function_pointer_t *func) {
1059
13.8k
    igraph_int_t i, def = -1, len = igraph_vector_ptr_size(&comb->list);
1060
1061
17.4k
    for (i = 0; i < len; i++) {
1062
17.4k
        igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[i];
1063
17.4k
        const char *n = rec->name;
1064
17.4k
        if ( (!name && !n) ||
1065
17.4k
             (name && n && !strcmp(n, name)) ) {
1066
13.8k
            *type = rec->type;
1067
13.8k
            *func = rec->func;
1068
13.8k
            return IGRAPH_SUCCESS;
1069
13.8k
        }
1070
3.58k
        if (!n) {
1071
0
            def = i;
1072
0
        }
1073
3.58k
    }
1074
1075
0
    if (def == -1) {
1076
        /* Did not find anything */
1077
0
        *type = IGRAPH_ATTRIBUTE_COMBINE_DEFAULT;
1078
0
        *func = 0;
1079
0
    } else {
1080
0
        igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[def];
1081
0
        *type = rec->type;
1082
0
        *func = rec->func;
1083
0
    }
1084
1085
0
    return IGRAPH_SUCCESS;
1086
13.8k
}
1087
1088
/**
1089
 * \function igraph_attribute_combination
1090
 * \brief Initialize attribute combination list and add records.
1091
 *
1092
 * \param comb The uninitialized attribute combination list.
1093
 * \param ...  A list of 'name, type[, func]', where:
1094
 * \param name The name of the attribute. If the name already exists
1095
 *             the attribute combination record will be replaced.
1096
 *             Use NULL to add a default combination record for all
1097
 *             atributes not in the list.
1098
 * \param type The type of the attribute combination. See \ref
1099
 *             igraph_attribute_combination_type_t for the options.
1100
 * \param func Function to be used if \p type is
1101
 *             \c IGRAPH_ATTRIBUTE_COMBINE_FUNCTION.
1102
 * The list is closed by setting the name to \c IGRAPH_NO_MORE_ATTRIBUTES.
1103
 * \return Error code.
1104
 *
1105
 * Time complexity: O(n^2), where n is the number attribute
1106
 *                  combinations records to add.
1107
 *
1108
 * \example examples/simple/igraph_attribute_combination.c
1109
 */
1110
igraph_error_t igraph_attribute_combination(
1111
5.90k
        igraph_attribute_combination_t *comb, ...) {
1112
1113
5.90k
    va_list ap;
1114
1115
5.90k
    IGRAPH_CHECK(igraph_attribute_combination_init(comb));
1116
1117
5.90k
    va_start(ap, comb);
1118
15.3k
    while (true) {
1119
15.3k
        igraph_function_pointer_t func = NULL;
1120
15.3k
        igraph_attribute_combination_type_t type;
1121
15.3k
        const char *name;
1122
1123
15.3k
        name = va_arg(ap, const char *);
1124
1125
15.3k
        if (name == IGRAPH_NO_MORE_ATTRIBUTES) {
1126
5.90k
            break;
1127
5.90k
        }
1128
1129
9.49k
        type = (igraph_attribute_combination_type_t) va_arg(ap, int);
1130
9.49k
        if (type == IGRAPH_ATTRIBUTE_COMBINE_FUNCTION) {
1131
0
            func = va_arg(ap, igraph_function_pointer_t);
1132
0
        }
1133
1134
9.49k
        if (strlen(name) == 0) {
1135
0
            name = 0;
1136
0
        }
1137
1138
9.49k
        igraph_error_t ret = igraph_attribute_combination_add(comb, name, type, func);
1139
9.49k
        if (ret != IGRAPH_SUCCESS) {
1140
0
            va_end(ap);
1141
0
            return ret;
1142
0
        }
1143
9.49k
    }
1144
1145
5.90k
    va_end(ap);
1146
1147
5.90k
    return IGRAPH_SUCCESS;
1148
5.90k
}