Coverage Report

Created: 2025-08-26 06:55

/src/libxmlb/src/xb-builder-node.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2018 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "XbSilo"
8
9
#include "config.h"
10
11
#include <gio/gio.h>
12
#include <string.h>
13
14
#include "xb-builder-node-private.h"
15
#include "xb-opcode-private.h"
16
#include "xb-silo-private.h"
17
#include "xb-string-private.h"
18
19
typedef struct {
20
  guint32 offset;
21
  gint priority;
22
  XbBuilderNodeFlags flags;
23
  gchar *element;
24
  guint32 element_idx;
25
  gchar *text;
26
  guint32 text_idx;
27
  gchar *tail;
28
  guint32 tail_idx;
29
  XbBuilderNode *parent; /* noref */
30
31
  /* Around 87% of all XML nodes have zero children, so this array is only
32
   * allocated if it’s non-empty. %NULL means an empty array. */
33
  GPtrArray *children; /* (element-type XbBuilderNode) (nullable) */
34
35
  /* Around 80% of all XML nodes have zero attributes, so this array is only
36
   * allocated if it’s non-empty. %NULL means an empty array. */
37
  GPtrArray *attrs; /* (element-type XbBuilderNodeAttr) (nullable) */
38
39
  /* Most nodes will have no tokens */
40
  GPtrArray *tokens;  /* (element-type utf8) (nullable) */
41
  GArray *token_idxs; /* (element-type guint32) (nullable) */
42
43
} XbBuilderNodePrivate;
44
45
G_DEFINE_TYPE_WITH_PRIVATE(XbBuilderNode, xb_builder_node, G_TYPE_OBJECT)
46
10.4M
#define GET_PRIVATE(o) (xb_builder_node_get_instance_private(o))
47
48
static void
49
xb_builder_node_attr_free(XbBuilderNodeAttr *attr);
50
51
/**
52
 * xb_builder_node_has_flag:
53
 * @self: a #XbBuilderNode
54
 * @flag: a #XbBuilderNodeFlags
55
 *
56
 * Checks a flag on the builder node.
57
 *
58
 * Returns: %TRUE if @flag is set
59
 *
60
 * Since: 0.1.0
61
 **/
62
gboolean
63
xb_builder_node_has_flag(XbBuilderNode *self, XbBuilderNodeFlags flag)
64
1.70M
{
65
1.70M
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
66
1.70M
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), FALSE);
67
1.70M
  return (priv->flags & flag) > 0;
68
1.70M
}
69
70
/**
71
 * xb_builder_node_add_flag:
72
 * @self: a #XbBuilderNode
73
 * @flag: a #XbBuilderNodeFlags
74
 *
75
 * Adds a flag to the builder node.
76
 *
77
 * Since: 0.1.0
78
 **/
79
void
80
xb_builder_node_add_flag(XbBuilderNode *self, XbBuilderNodeFlags flag)
81
0
{
82
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
83
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
84
85
0
  if ((priv->flags & flag) != 0)
86
0
    return;
87
88
  /* do in-place */
89
0
  if ((flag & XB_BUILDER_NODE_FLAG_STRIP_TEXT) > 0 && priv->text != NULL)
90
0
    g_strstrip(priv->text);
91
92
0
  priv->flags |= flag;
93
0
  for (guint i = 0; priv->children != NULL && i < priv->children->len; i++) {
94
0
    XbBuilderNode *c = g_ptr_array_index(priv->children, i);
95
0
    xb_builder_node_add_flag(c, flag);
96
0
  }
97
0
}
98
99
/**
100
 * xb_builder_node_get_element:
101
 * @self: a #XbBuilderNode
102
 *
103
 * Gets the element from the builder node.
104
 *
105
 * Returns: string, or %NULL if unset
106
 *
107
 * Since: 0.1.0
108
 **/
109
const gchar *
110
xb_builder_node_get_element(XbBuilderNode *self)
111
0
{
112
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
113
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
114
0
  return priv->element;
115
0
}
116
117
/**
118
 * xb_builder_node_set_element:
119
 * @self: a #XbBuilderNode
120
 * @element: a string element
121
 *
122
 * Sets the element name on the builder node.
123
 *
124
 * Since: 0.1.0
125
 **/
126
void
127
xb_builder_node_set_element(XbBuilderNode *self, const gchar *element)
128
0
{
129
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
130
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
131
0
  g_free(priv->element);
132
0
  priv->element = g_strdup(element);
133
0
}
134
135
/**
136
 * xb_builder_node_get_attr:
137
 * @self: a #XbBuilderNode
138
 * @name: attribute name, e.g. `type`
139
 *
140
 * Gets an attribute from the builder node.
141
 *
142
 * Returns: string, or %NULL if unset
143
 *
144
 * Since: 0.1.0
145
 **/
146
const gchar *
147
xb_builder_node_get_attr(XbBuilderNode *self, const gchar *name)
148
0
{
149
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
150
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
151
0
  g_return_val_if_fail(name != NULL, NULL);
152
153
0
  if (priv->attrs == NULL)
154
0
    return NULL;
155
156
0
  for (guint i = 0; i < priv->attrs->len; i++) {
157
0
    XbBuilderNodeAttr *a = g_ptr_array_index(priv->attrs, i);
158
0
    if (g_strcmp0(a->name, name) == 0)
159
0
      return a->value;
160
0
  }
161
0
  return NULL;
162
0
}
163
164
/**
165
 * xb_builder_node_get_attr_as_uint:
166
 * @self: a #XbBuilderNode
167
 * @name: attribute name, e.g. `priority`
168
 *
169
 * Gets an attribute from the builder node.
170
 *
171
 * Returns: integer, or 0 if unset
172
 *
173
 * Since: 0.1.3
174
 **/
175
guint64
176
xb_builder_node_get_attr_as_uint(XbBuilderNode *self, const gchar *name)
177
0
{
178
0
  const gchar *tmp = xb_builder_node_get_attr(self, name);
179
0
  if (tmp == NULL)
180
0
    return 0;
181
0
  if (g_str_has_prefix(tmp, "0x"))
182
0
    return g_ascii_strtoull(tmp + 2, NULL, 16);
183
0
  return g_ascii_strtoll(tmp, NULL, 10);
184
0
}
185
186
/**
187
 * xb_builder_node_get_text:
188
 * @self: a #XbBuilderNode
189
 *
190
 * Gets the text from the builder node.
191
 *
192
 * Returns: string, or %NULL if unset
193
 *
194
 * Since: 0.1.0
195
 **/
196
const gchar *
197
xb_builder_node_get_text(XbBuilderNode *self)
198
0
{
199
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
200
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
201
0
  return priv->text;
202
0
}
203
204
/**
205
 * xb_builder_node_get_text_as_uint:
206
 * @self: a #XbBuilderNode
207
 *
208
 * Gets the text from the builder node.
209
 *
210
 * Returns: integer, or 0 if unset
211
 *
212
 * Since: 0.1.3
213
 **/
214
guint64
215
xb_builder_node_get_text_as_uint(XbBuilderNode *self)
216
0
{
217
0
  const gchar *tmp = xb_builder_node_get_text(self);
218
0
  if (tmp == NULL)
219
0
    return 0;
220
0
  if (g_str_has_prefix(tmp, "0x"))
221
0
    return g_ascii_strtoull(tmp + 2, NULL, 16);
222
0
  return g_ascii_strtoll(tmp, NULL, 10);
223
0
}
224
225
/**
226
 * xb_builder_node_get_tail:
227
 * @self: a #XbBuilderNode
228
 *
229
 * Gets the tail from the builder node.
230
 *
231
 * Returns: string, or %NULL if unset
232
 *
233
 * Since: 0.1.12
234
 **/
235
const gchar *
236
xb_builder_node_get_tail(XbBuilderNode *self)
237
0
{
238
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
239
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
240
0
  return priv->tail;
241
0
}
242
243
/* private */
244
/* Returns NULL if the array is empty */
245
GPtrArray *
246
xb_builder_node_get_attrs(XbBuilderNode *self)
247
0
{
248
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
249
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
250
0
  return priv->attrs;
251
0
}
252
253
static gchar *
254
xb_builder_node_parse_literal_text(XbBuilderNode *self, const gchar *text, gssize text_len)
255
692k
{
256
692k
  GString *tmp;
257
692k
  guint newline_count = 0;
258
692k
  g_auto(GStrv) split = NULL;
259
692k
  gsize text_len_safe;
260
261
  /* sanity check */
262
692k
  if (text == NULL)
263
0
    return NULL;
264
265
  /* we know this has been pre-fixed */
266
692k
  text_len_safe = text_len >= 0 ? (gsize)text_len : strlen(text);
267
692k
  if (xb_builder_node_has_flag(self, XB_BUILDER_NODE_FLAG_LITERAL_TEXT))
268
0
    return g_strndup(text, text_len_safe);
269
270
  /* all whitespace? */
271
692k
  if (xb_string_isspace(text, text_len_safe))
272
0
    return NULL;
273
274
  /* all on one line, no trailing or leading whitespace */
275
692k
  if (g_strstr_len(text, text_len, "\n") == NULL)
276
691k
    return g_strndup(text, text_len_safe);
277
278
  /* split the text into lines */
279
1.29k
  tmp = g_string_sized_new((gsize)text_len_safe + 1);
280
1.29k
  split = g_strsplit(text, "\n", -1);
281
4.64k
  for (guint i = 0; split[i] != NULL; i++) {
282
    /* if this is a blank line we end the paragraph mode
283
     * and swallow the newline. If we see exactly two
284
     * newlines in sequence then do a paragraph break */
285
3.35k
    if (split[i][0] == '\0') {
286
1.64k
      newline_count++;
287
1.64k
      continue;
288
1.64k
    }
289
290
    /* if the line just before this one was not a newline
291
     * then separate the words with a space */
292
1.70k
    if (newline_count == 1 && tmp->len > 0)
293
310
      g_string_append(tmp, " ");
294
295
    /* if we had more than one newline in sequence add a paragraph
296
     * break */
297
1.70k
    if (newline_count > 1)
298
426
      g_string_append(tmp, "\n\n");
299
300
    /* add the actual stripped text */
301
1.70k
    g_string_append(tmp, split[i]);
302
303
    /* this last section was paragraph */
304
1.70k
    newline_count = 1;
305
1.70k
  }
306
307
  /* success */
308
1.29k
  return g_string_free(tmp, FALSE);
309
692k
}
310
311
/**
312
 * xb_builder_node_tokenize_text:
313
 * @self: a #XbBuilderNode
314
 *
315
 * Tokenize text added with xb_builder_node_set_text().
316
 *
317
 * When searching, libxmlb often has to tokenize strings before they can be
318
 * compared. This is done in the "fast path" and makes searching for non-ASCII
319
 * text much slower.
320
 *
321
 * Adding the tokens to the deduplicated string table allows much faster
322
 * searching at the expense of a ~5% size increase of the silo.
323
 *
324
 * This function adds all valid UTF-8 and ASCII search words generated from
325
 * the value of xb_builder_node_set_text().
326
 *
327
 * The transliteration locale (e.g. `en_GB`) is read from the `xml:lang`
328
 * node attribute if set.
329
 *
330
 * Since: 0.3.1
331
 **/
332
void
333
xb_builder_node_tokenize_text(XbBuilderNode *self)
334
0
{
335
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
336
0
  const gchar *xml_lang = xb_builder_node_get_attr(self, "xml:lang");
337
0
  guint ascii_tokens_sz;
338
0
  guint tokens_sz;
339
0
  g_autofree gchar **ascii_tokens = NULL;
340
0
  g_autofree gchar **tokens = NULL;
341
342
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
343
344
0
  if (priv->text == NULL)
345
0
    return;
346
0
  tokens = g_str_tokenize_and_fold(priv->text, xml_lang, &ascii_tokens);
347
348
  /* preallocate the right array size (and more for invalid tokens) */
349
0
  tokens_sz = g_strv_length(tokens);
350
0
  ascii_tokens_sz = g_strv_length(ascii_tokens);
351
0
  if (priv->tokens == NULL)
352
0
    priv->tokens = g_ptr_array_new_full(tokens_sz + ascii_tokens_sz, g_free);
353
354
  /* add all valid UTF-8 and ASCII tokens */
355
0
  for (guint i = 0; i < tokens_sz; i++) {
356
0
    if (!xb_string_token_valid(tokens[i])) {
357
0
      g_free(g_steal_pointer(&tokens[i]));
358
0
      continue;
359
0
    }
360
0
    g_ptr_array_add(priv->tokens, g_steal_pointer(&tokens[i]));
361
0
  }
362
0
  for (guint i = 0; i < ascii_tokens_sz; i++) {
363
0
    if (!xb_string_token_valid(ascii_tokens[i])) {
364
0
      g_free(g_steal_pointer(&ascii_tokens[i]));
365
0
      continue;
366
0
    }
367
0
    g_ptr_array_add(priv->tokens, g_steal_pointer(&ascii_tokens[i]));
368
0
  }
369
370
  /* add this so we can set XbSiloNodeFlag.TOKENIZE_TEXT */
371
0
  xb_builder_node_add_flag(self, XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT);
372
0
}
373
374
/**
375
 * xb_builder_node_set_text:
376
 * @self: a #XbBuilderNode
377
 * @text: (allow-none): a string
378
 * @text_len: length of @text, or -1 if @text is NUL terminated
379
 *
380
 * Sets the text on the builder node.
381
 *
382
 * Since: 0.1.0
383
 **/
384
void
385
xb_builder_node_set_text(XbBuilderNode *self, const gchar *text, gssize text_len)
386
692k
{
387
692k
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
388
389
692k
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
390
391
  /* old data */
392
692k
  g_free(priv->text);
393
692k
  priv->text = xb_builder_node_parse_literal_text(self, text, text_len);
394
692k
  priv->flags |= XB_BUILDER_NODE_FLAG_HAS_TEXT;
395
396
  /* strip before tokenization */
397
692k
  if ((priv->flags & XB_BUILDER_NODE_FLAG_STRIP_TEXT) > 0 && priv->text != NULL)
398
0
    g_strstrip(priv->text);
399
400
  /* tokenize */
401
692k
  if (priv->flags & XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT)
402
0
    xb_builder_node_tokenize_text(self);
403
692k
}
404
405
/**
406
 * xb_builder_node_set_tail:
407
 * @self: a #XbBuilderNode
408
 * @tail: (allow-none): a string
409
 * @tail_len: length of @tail, or -1 if @tail is NUL terminated
410
 *
411
 * Sets the tail on the builder node.
412
 *
413
 * Since: 0.1.12
414
 **/
415
void
416
xb_builder_node_set_tail(XbBuilderNode *self, const gchar *tail, gssize tail_len)
417
0
{
418
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
419
420
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
421
422
  /* old data */
423
0
  g_free(priv->tail);
424
0
  priv->tail = xb_builder_node_parse_literal_text(self, tail, tail_len);
425
0
  priv->flags |= XB_BUILDER_NODE_FLAG_HAS_TAIL;
426
0
}
427
428
/**
429
 * xb_builder_node_set_attr:
430
 * @self: a #XbBuilderNode
431
 * @name: attribute name, e.g. `type`
432
 * @value: attribute value, e.g. `desktop`
433
 *
434
 * Adds an attribute to the builder node.
435
 *
436
 * Since: 0.1.0
437
 **/
438
void
439
xb_builder_node_set_attr(XbBuilderNode *self, const gchar *name, const gchar *value)
440
501k
{
441
501k
  XbBuilderNodeAttr *a;
442
501k
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
443
444
501k
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
445
501k
  g_return_if_fail(name != NULL);
446
447
501k
  if (priv->attrs == NULL)
448
335k
    priv->attrs =
449
335k
        g_ptr_array_new_with_free_func((GDestroyNotify)xb_builder_node_attr_free);
450
451
  /* check for existing name */
452
667k
  for (guint i = 0; i < priv->attrs->len; i++) {
453
165k
    a = g_ptr_array_index(priv->attrs, i);
454
165k
    if (g_strcmp0(a->name, name) == 0) {
455
0
      g_free(a->value);
456
0
      a->value = g_strdup(value);
457
0
      return;
458
0
    }
459
165k
  }
460
461
  /* create new */
462
501k
  a = g_slice_new0(XbBuilderNodeAttr);
463
501k
  a->name = g_strdup(name);
464
501k
  a->name_idx = XB_SILO_UNSET;
465
501k
  a->value = g_strdup(value);
466
501k
  a->value_idx = XB_SILO_UNSET;
467
501k
  g_ptr_array_add(priv->attrs, a);
468
501k
}
469
470
/**
471
 * xb_builder_node_remove_attr:
472
 * @self: a #XbBuilderNode
473
 * @name: attribute name, e.g. `type`
474
 *
475
 * Removes an attribute from the builder node.
476
 *
477
 * Since: 0.1.0
478
 **/
479
void
480
xb_builder_node_remove_attr(XbBuilderNode *self, const gchar *name)
481
0
{
482
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
483
484
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
485
0
  g_return_if_fail(name != NULL);
486
487
0
  if (priv->attrs == NULL)
488
0
    return;
489
490
0
  for (guint i = 0; i < priv->attrs->len; i++) {
491
0
    XbBuilderNodeAttr *a = g_ptr_array_index(priv->attrs, i);
492
0
    if (g_strcmp0(a->name, name) == 0) {
493
0
      g_ptr_array_remove_index(priv->attrs, i);
494
0
      break;
495
0
    }
496
0
  }
497
0
}
498
499
/**
500
 * xb_builder_node_depth:
501
 * @self: a #XbBuilderNode
502
 *
503
 * Gets the depth of the node tree, where 0 is the root node.
504
 *
505
 * Since: 0.1.1
506
 **/
507
guint
508
xb_builder_node_depth(XbBuilderNode *self)
509
0
{
510
0
  for (guint i = 0;; i++) {
511
0
    XbBuilderNodePrivate *priv = GET_PRIVATE(self);
512
0
    if (priv->parent == NULL)
513
0
      return i;
514
0
    self = priv->parent;
515
0
  }
516
0
  return 0;
517
0
}
518
519
/**
520
 * xb_builder_node_add_child:
521
 * @self: A XbBuilderNode
522
 * @child: A XbBuilderNode
523
 *
524
 * Adds a child builder node.
525
 *
526
 * Since: 0.1.0
527
 **/
528
void
529
xb_builder_node_add_child(XbBuilderNode *self, XbBuilderNode *child)
530
840k
{
531
840k
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
532
840k
  XbBuilderNodePrivate *priv_child = GET_PRIVATE(child);
533
840k
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
534
840k
  g_return_if_fail(XB_IS_BUILDER_NODE(child));
535
840k
  g_return_if_fail(priv_child->parent == NULL);
536
537
  /* no refcount */
538
840k
  priv_child->parent = self;
539
540
840k
  if (priv->children == NULL)
541
169k
    priv->children = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
542
543
840k
  g_ptr_array_add(priv->children, g_object_ref(child));
544
840k
}
545
546
/**
547
 * xb_builder_node_remove_child:
548
 * @self: A XbBuilderNode
549
 * @child: A XbBuilderNode
550
 *
551
 * Removes a child builder node.
552
 *
553
 * Since: 0.1.1
554
 **/
555
void
556
xb_builder_node_remove_child(XbBuilderNode *self, XbBuilderNode *child)
557
0
{
558
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
559
0
  XbBuilderNodePrivate *priv_child = GET_PRIVATE(child);
560
561
  /* no refcount */
562
0
  priv_child->parent = NULL;
563
564
0
  if (priv->children != NULL)
565
0
    g_ptr_array_remove(priv->children, child);
566
0
}
567
568
/**
569
 * xb_builder_node_unlink:
570
 * @self: a #XbBuilderNode
571
 *
572
 * Unlinks a #XbBuilderNode from a tree, resulting in two separate trees.
573
 *
574
 * This should not be used from the function called by xb_builder_node_traverse()
575
 * otherwise the entire tree will not be traversed.
576
 *
577
 * Instead use xb_builder_node_add_flag(bn,XB_BUILDER_NODE_FLAG_IGNORE);
578
 *
579
 * Since: 0.1.1
580
 **/
581
void
582
xb_builder_node_unlink(XbBuilderNode *self)
583
0
{
584
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
585
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
586
0
  if (priv->parent == NULL)
587
0
    return;
588
0
  xb_builder_node_remove_child(priv->parent, self);
589
0
}
590
591
/**
592
 * xb_builder_node_get_parent:
593
 * @self: a #XbBuilderNode
594
 *
595
 * Gets the parent node for the current node.
596
 *
597
 * Returns: (transfer full): a new #XbBuilderNode, or %NULL no parent exists.
598
 *
599
 * Since: 0.1.1
600
 **/
601
XbBuilderNode *
602
xb_builder_node_get_parent(XbBuilderNode *self)
603
0
{
604
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
605
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
606
0
  if (priv->parent == NULL)
607
0
    return NULL;
608
0
  return g_object_ref(priv->parent);
609
0
}
610
611
/**
612
 * xb_builder_node_get_children:
613
 * @self: a #XbBuilderNode
614
 *
615
 * Gets the children of the builder node.
616
 *
617
 * Returns: (transfer none) (element-type XbBuilderNode): children
618
 *
619
 * Since: 0.1.0
620
 **/
621
GPtrArray *
622
xb_builder_node_get_children(XbBuilderNode *self)
623
0
{
624
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
625
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
626
627
  /* For backwards compatibility reasons we have to return a non-%NULL
628
   * array here. */
629
0
  if (priv->children == NULL)
630
0
    priv->children = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
631
632
0
  return priv->children;
633
0
}
634
635
/**
636
 * xb_builder_node_get_first_child:
637
 * @self: a #XbBuilderNode
638
 *
639
 * Gets the first child of the builder node.
640
 *
641
 * Returns: (transfer none): a #XbBuilderNode, or %NULL
642
 *
643
 * Since: 0.1.12
644
 **/
645
XbBuilderNode *
646
xb_builder_node_get_first_child(XbBuilderNode *self)
647
0
{
648
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
649
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
650
0
  if (priv->children == NULL || priv->children->len == 0)
651
0
    return NULL;
652
0
  return g_ptr_array_index(priv->children, 0);
653
0
}
654
655
/**
656
 * xb_builder_node_get_last_child:
657
 * @self: a #XbBuilderNode
658
 *
659
 * Gets the last child of the builder node.
660
 *
661
 * Returns: (transfer none): a #XbBuilderNode, or %NULL
662
 *
663
 * Since: 0.1.12
664
 **/
665
XbBuilderNode *
666
xb_builder_node_get_last_child(XbBuilderNode *self)
667
0
{
668
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
669
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
670
0
  if (priv->children == NULL || priv->children->len == 0)
671
0
    return NULL;
672
0
  return g_ptr_array_index(priv->children, priv->children->len - 1);
673
0
}
674
675
/**
676
 * xb_builder_node_get_child:
677
 * @self: a #XbBuilderNode
678
 * @element: An element name, e.g. "url"
679
 * @text: (allow-none): node text, e.g. "gimp.desktop"
680
 *
681
 * Finds a child builder node by the element name, and optionally text value.
682
 *
683
 * Returns: (transfer full): a new #XbBuilderNode, or %NULL if not found
684
 *
685
 * Since: 0.1.1
686
 **/
687
XbBuilderNode *
688
xb_builder_node_get_child(XbBuilderNode *self, const gchar *element, const gchar *text)
689
0
{
690
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
691
692
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
693
0
  g_return_val_if_fail(element != NULL, NULL);
694
695
0
  if (priv->children == NULL)
696
0
    return NULL;
697
698
0
  for (guint i = 0; i < priv->children->len; i++) {
699
0
    XbBuilderNode *child = g_ptr_array_index(priv->children, i);
700
0
    if (g_strcmp0(xb_builder_node_get_element(child), element) != 0)
701
0
      continue;
702
0
    if (text != NULL && g_strcmp0(xb_builder_node_get_text(child), text) != 0)
703
0
      continue;
704
0
    return g_object_ref(child);
705
0
  }
706
0
  return NULL;
707
0
}
708
709
typedef struct {
710
  gint max_depth;
711
  XbBuilderNodeTraverseFunc func;
712
  gpointer user_data;
713
  GTraverseFlags flags;
714
  GTraverseType order;
715
} XbBuilderNodeTraverseHelper;
716
717
static void
718
xb_builder_node_traverse_cb(XbBuilderNodeTraverseHelper *helper, XbBuilderNode *bn, gint depth)
719
0
{
720
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(bn);
721
0
  GPtrArray *children = priv->children;
722
723
  /* only leaves */
724
0
  if (helper->flags == G_TRAVERSE_LEAVES && children != NULL && children->len > 0)
725
0
    return;
726
727
  /* only non-leaves */
728
0
  if (helper->flags == G_TRAVERSE_NON_LEAVES && (children == NULL || children->len == 0))
729
0
    return;
730
731
  /* recurse */
732
0
  if (helper->order == G_PRE_ORDER) {
733
0
    if (helper->func(bn, helper->user_data))
734
0
      return;
735
0
  }
736
0
  if ((helper->max_depth < 0 || depth < helper->max_depth) && children != NULL) {
737
0
    for (guint i = 0; i < children->len; i++) {
738
0
      XbBuilderNode *bc = g_ptr_array_index(children, i);
739
0
      xb_builder_node_traverse_cb(helper, bc, depth + 1);
740
0
    }
741
0
  }
742
0
  if (helper->order == G_POST_ORDER) {
743
0
    if (helper->func(bn, helper->user_data))
744
0
      return;
745
0
  }
746
0
}
747
748
/**
749
 * xb_builder_node_traverse:
750
 * @self: a #XbBuilderNode
751
 * @order: a #GTraverseType, e.g. %G_PRE_ORDER
752
 * @flags: a #GTraverseFlags, e.g. %G_TRAVERSE_ALL
753
 * @max_depth: the maximum depth of the traversal, or -1 for no limit
754
 * @func: (scope call): a #XbBuilderNodeTraverseFunc
755
 * @user_data: user pointer to pass to @func, or %NULL
756
 *
757
 * Traverses a tree starting from @self. It calls the given function for each
758
 * node visited.
759
 *
760
 * The traversal can be halted at any point by returning TRUE from @func.
761
 *
762
 * Since: 0.1.1
763
 **/
764
void
765
xb_builder_node_traverse(XbBuilderNode *self,
766
       GTraverseType order,
767
       GTraverseFlags flags,
768
       gint max_depth,
769
       XbBuilderNodeTraverseFunc func,
770
       gpointer user_data)
771
0
{
772
0
  XbBuilderNodeTraverseHelper helper = {
773
0
      .max_depth = max_depth,
774
0
      .order = order,
775
0
      .flags = flags,
776
0
      .func = func,
777
0
      .user_data = user_data,
778
0
  };
779
0
  if (order == G_PRE_ORDER || order == G_POST_ORDER) {
780
0
    xb_builder_node_traverse_cb(&helper, self, 0);
781
0
    return;
782
0
  }
783
0
  g_critical("order %u not supported", order);
784
0
}
785
786
typedef struct {
787
  XbBuilderNodeSortFunc func;
788
  gpointer user_data;
789
} XbBuilderNodeSortHelper;
790
791
static gint
792
xb_builder_node_sort_children_cb(gconstpointer a, gconstpointer b, gpointer user_data)
793
0
{
794
0
  XbBuilderNodeSortHelper *helper = (XbBuilderNodeSortHelper *)user_data;
795
0
  XbBuilderNode *bn1 = *((XbBuilderNode **)a);
796
0
  XbBuilderNode *bn2 = *((XbBuilderNode **)b);
797
0
  return helper->func(bn1, bn2, helper->user_data);
798
0
}
799
800
/**
801
 * xb_builder_node_sort_children:
802
 * @self: a #XbBuilderNode
803
 * @func: (scope call): a #XbBuilderNodeSortFunc
804
 * @user_data: user pointer to pass to @func, or %NULL
805
 *
806
 * Sorts the node children using a custom sort function.
807
 *
808
 * Since: 0.1.3
809
 **/
810
void
811
xb_builder_node_sort_children(XbBuilderNode *self, XbBuilderNodeSortFunc func, gpointer user_data)
812
0
{
813
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
814
0
  XbBuilderNodeSortHelper helper = {
815
0
      .func = func,
816
0
      .user_data = user_data,
817
0
  };
818
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
819
0
  g_return_if_fail(func != NULL);
820
821
0
  if (priv->children == NULL)
822
0
    return;
823
824
0
  g_ptr_array_sort_with_data(priv->children, xb_builder_node_sort_children_cb, &helper);
825
0
}
826
827
/* private */
828
guint32
829
xb_builder_node_get_offset(XbBuilderNode *self)
830
0
{
831
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
832
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0);
833
0
  return priv->offset;
834
0
}
835
836
/* private */
837
void
838
xb_builder_node_set_offset(XbBuilderNode *self, guint32 offset)
839
0
{
840
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
841
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
842
0
  priv->offset = offset;
843
0
}
844
845
/* private */
846
gint
847
xb_builder_node_get_priority(XbBuilderNode *self)
848
0
{
849
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
850
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0);
851
0
  return priv->priority;
852
0
}
853
854
/* private */
855
void
856
xb_builder_node_set_priority(XbBuilderNode *self, gint priority)
857
0
{
858
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
859
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
860
0
  priv->priority = priority;
861
0
}
862
863
/* private */
864
guint32
865
xb_builder_node_get_element_idx(XbBuilderNode *self)
866
0
{
867
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
868
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0);
869
0
  return priv->element_idx;
870
0
}
871
872
/* private */
873
void
874
xb_builder_node_set_element_idx(XbBuilderNode *self, guint32 element_idx)
875
0
{
876
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
877
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
878
0
  priv->element_idx = element_idx;
879
0
}
880
881
/* private */
882
guint32
883
xb_builder_node_get_text_idx(XbBuilderNode *self)
884
0
{
885
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
886
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0);
887
0
  return priv->text_idx;
888
0
}
889
890
/* private */
891
void
892
xb_builder_node_set_text_idx(XbBuilderNode *self, guint32 text_idx)
893
0
{
894
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
895
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
896
0
  priv->text_idx = text_idx;
897
0
}
898
899
/* private */
900
guint32
901
xb_builder_node_get_tail_idx(XbBuilderNode *self)
902
0
{
903
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
904
0
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0);
905
0
  return priv->tail_idx;
906
0
}
907
908
/* private */
909
void
910
xb_builder_node_set_tail_idx(XbBuilderNode *self, guint32 tail_idx)
911
0
{
912
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
913
0
  g_return_if_fail(XB_IS_BUILDER_NODE(self));
914
0
  priv->tail_idx = tail_idx;
915
0
}
916
917
/* private */
918
guint32
919
xb_builder_node_size(XbBuilderNode *self)
920
0
{
921
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
922
0
  guint32 sz = sizeof(XbSiloNode);
923
0
  gsize attr_len = (priv->attrs != NULL) ? priv->attrs->len : 0;
924
0
  gsize token_len = (priv->tokens != NULL) ? MIN(priv->tokens->len, XB_OPCODE_TOKEN_MAX) : 0;
925
0
  return sz + attr_len * sizeof(XbSiloNodeAttr) + token_len * sizeof(guint32);
926
0
}
927
928
static void
929
xb_builder_node_attr_free(XbBuilderNodeAttr *attr)
930
501k
{
931
501k
  g_free(attr->name);
932
501k
  g_free(attr->value);
933
501k
  g_slice_free(XbBuilderNodeAttr, attr);
934
501k
}
935
936
static void
937
xb_builder_node_init(XbBuilderNode *self)
938
1.00M
{
939
1.00M
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
940
1.00M
  priv->element_idx = XB_SILO_UNSET;
941
1.00M
  priv->text_idx = XB_SILO_UNSET;
942
1.00M
  priv->tail_idx = XB_SILO_UNSET;
943
1.00M
  priv->attrs = NULL;    /* only allocated when an attribute is added */
944
1.00M
  priv->children = NULL; /* only allocated when a child is added */
945
1.00M
}
946
947
static void
948
xb_builder_node_dispose(GObject *obj)
949
1.00M
{
950
1.00M
  XbBuilderNode *self = XB_BUILDER_NODE(obj);
951
1.00M
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
952
953
  /* clear all the child nodes’ parent pointers */
954
1.00M
  if (priv->children != NULL) {
955
1.00M
    for (guint i = 0; i < priv->children->len; i++) {
956
840k
      XbBuilderNode *child = g_ptr_array_index(priv->children, i);
957
840k
      XbBuilderNodePrivate *priv_child = GET_PRIVATE(child);
958
840k
      priv_child->parent = NULL;
959
840k
    }
960
169k
  }
961
962
1.00M
  G_OBJECT_CLASS(xb_builder_node_parent_class)->dispose(obj);
963
1.00M
}
964
965
static void
966
xb_builder_node_finalize(GObject *obj)
967
1.00M
{
968
1.00M
  XbBuilderNode *self = XB_BUILDER_NODE(obj);
969
1.00M
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
970
1.00M
  g_free(priv->element);
971
1.00M
  g_free(priv->text);
972
1.00M
  g_free(priv->tail);
973
1.00M
  g_clear_pointer(&priv->attrs, g_ptr_array_unref);
974
1.00M
  g_clear_pointer(&priv->children, g_ptr_array_unref);
975
1.00M
  g_clear_pointer(&priv->tokens, g_ptr_array_unref);
976
1.00M
  g_clear_pointer(&priv->token_idxs, g_array_unref);
977
1.00M
  G_OBJECT_CLASS(xb_builder_node_parent_class)->finalize(obj);
978
1.00M
}
979
980
static void
981
xb_builder_node_class_init(XbBuilderNodeClass *klass)
982
1
{
983
1
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
984
985
1
  object_class->dispose = xb_builder_node_dispose;
986
1
  object_class->finalize = xb_builder_node_finalize;
987
1
}
988
989
/**
990
 * xb_builder_node_new:
991
 * @element: An element name, e.g. "component"
992
 *
993
 * Creates a new builder node.
994
 *
995
 * Returns: (transfer full): a new #XbBuilderNode
996
 *
997
 * Since: 0.1.0
998
 **/
999
XbBuilderNode *
1000
xb_builder_node_new(const gchar *element)
1001
1.00M
{
1002
1.00M
  XbBuilderNode *self = g_object_new(XB_TYPE_BUILDER_NODE, NULL);
1003
1.00M
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
1004
1.00M
  priv->element = g_strdup(element);
1005
1.00M
  return self;
1006
1.00M
}
1007
1008
/**
1009
 * xb_builder_node_insert: (skip)
1010
 * @parent: A XbBuilderNode, or %NULL
1011
 * @element: An element name, e.g. "component"
1012
 * @...: any attributes to add to the node, terminated by %NULL
1013
 *
1014
 * Creates a new builder node.
1015
 *
1016
 * Returns: (transfer full): a new #XbBuilderNode
1017
 *
1018
 * Since: 0.1.0
1019
 **/
1020
XbBuilderNode *
1021
xb_builder_node_insert(XbBuilderNode *parent, const gchar *element, ...)
1022
0
{
1023
0
  XbBuilderNode *self = xb_builder_node_new(element);
1024
0
  va_list args;
1025
0
  const gchar *key;
1026
0
  const gchar *value;
1027
1028
  /* add this node to the parent */
1029
0
  if (parent != NULL)
1030
0
    xb_builder_node_add_child(parent, self);
1031
1032
  /* process the attrs valist */
1033
0
  va_start(args, element);
1034
0
  for (guint i = 0;; i++) {
1035
0
    key = va_arg(args, const gchar *);
1036
0
    if (key == NULL)
1037
0
      break;
1038
0
    value = va_arg(args, const gchar *);
1039
0
    if (value == NULL)
1040
0
      break;
1041
0
    xb_builder_node_set_attr(self, key, value);
1042
0
  }
1043
0
  va_end(args);
1044
1045
0
  return self;
1046
0
}
1047
1048
/**
1049
 * xb_builder_node_insert_text: (skip)
1050
 * @parent: A XbBuilderNode, or %NULL
1051
 * @element: An element name, e.g. "id"
1052
 * @text: (allow-none): node text, e.g. "gimp.desktop"
1053
 * @...: any attributes to add to the node, terminated by %NULL
1054
 *
1055
 * Creates a new builder node with optional node text.
1056
 *
1057
 * Since: 0.1.0
1058
 **/
1059
void
1060
xb_builder_node_insert_text(XbBuilderNode *parent, const gchar *element, const gchar *text, ...)
1061
840k
{
1062
840k
  g_autoptr(XbBuilderNode) self = xb_builder_node_new(element);
1063
840k
  va_list args;
1064
840k
  const gchar *key;
1065
840k
  const gchar *value;
1066
1067
840k
  g_return_if_fail(parent != NULL);
1068
1069
  /* add this node to the parent */
1070
840k
  xb_builder_node_add_child(parent, self);
1071
840k
  if (text != NULL)
1072
692k
    xb_builder_node_set_text(self, text, -1);
1073
1074
  /* process the attrs valist */
1075
840k
  va_start(args, text);
1076
1.17M
  for (guint i = 0;; i++) {
1077
1.17M
    key = va_arg(args, const gchar *);
1078
1.17M
    if (key == NULL)
1079
840k
      break;
1080
331k
    value = va_arg(args, const gchar *);
1081
331k
    if (value == NULL)
1082
0
      break;
1083
331k
    xb_builder_node_set_attr(self, key, value);
1084
331k
  }
1085
840k
  va_end(args);
1086
840k
}
1087
1088
typedef struct {
1089
  GString *xml;
1090
  XbNodeExportFlags flags;
1091
  guint level;
1092
} XbBuilderNodeExportHelper;
1093
1094
static gboolean
1095
xb_builder_node_export_helper(XbBuilderNode *self,
1096
            XbBuilderNodeExportHelper *helper,
1097
            GError **error)
1098
1.00M
{
1099
1.00M
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
1100
1101
  /* do not output */
1102
1.00M
  if (xb_builder_node_has_flag(self, XB_BUILDER_NODE_FLAG_IGNORE))
1103
0
    return TRUE;
1104
1105
  /* add start of opening tag */
1106
1.00M
  if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) {
1107
1.85M
    for (guint i = 0; i < helper->level; i++)
1108
840k
      g_string_append(helper->xml, "  ");
1109
1.00M
  }
1110
1.00M
  g_string_append_printf(helper->xml, "<%s", priv->element);
1111
1112
  /* add any attributes */
1113
1.51M
  for (guint i = 0; priv->attrs != NULL && i < priv->attrs->len; i++) {
1114
501k
    XbBuilderNodeAttr *a = g_ptr_array_index(priv->attrs, i);
1115
501k
    g_autofree gchar *key = xb_string_xml_escape(a->name);
1116
501k
    g_autofree gchar *val = xb_string_xml_escape(a->value);
1117
501k
    g_string_append_printf(helper->xml, " %s=\"%s\"", key, val);
1118
501k
  }
1119
1120
1.00M
  if (helper->flags & XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY && priv->text == NULL &&
1121
1.00M
      priv->children == NULL) {
1122
147k
    g_string_append(helper->xml, " />");
1123
862k
  } else {
1124
    /* finish the opening tag and add any text if it exists */
1125
862k
    if (priv->text != NULL) {
1126
692k
      g_autofree gchar *text = xb_string_xml_escape(priv->text);
1127
692k
      g_string_append(helper->xml, ">");
1128
692k
      g_string_append(helper->xml, text);
1129
692k
    } else {
1130
169k
      g_string_append(helper->xml, ">");
1131
169k
      if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE)
1132
169k
        g_string_append(helper->xml, "\n");
1133
169k
    }
1134
1135
    /* recurse deeper */
1136
1.70M
    for (guint i = 0; priv->children != NULL && i < priv->children->len; i++) {
1137
840k
      XbBuilderNode *child = g_ptr_array_index(priv->children, i);
1138
840k
      helper->level++;
1139
840k
      if (!xb_builder_node_export_helper(child, helper, error))
1140
0
        return FALSE;
1141
840k
      helper->level--;
1142
840k
    }
1143
1144
    /* add closing tag */
1145
862k
    if ((helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) > 0 && priv->text == NULL) {
1146
169k
      for (guint i = 0; i < helper->level; i++)
1147
0
        g_string_append(helper->xml, "  ");
1148
169k
    }
1149
862k
    g_string_append_printf(helper->xml, "</%s>", priv->element);
1150
862k
  }
1151
1152
  /* add any tail if it exists */
1153
1.00M
  if (priv->tail != NULL) {
1154
0
    g_autofree gchar *tail = xb_string_xml_escape(priv->tail);
1155
0
    g_string_append(helper->xml, tail);
1156
0
  }
1157
1158
1.00M
  if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE)
1159
1.00M
    g_string_append(helper->xml, "\n");
1160
1.00M
  return TRUE;
1161
1.00M
}
1162
1163
/**
1164
 * xb_builder_node_export:
1165
 * @self: a #XbBuilderNode
1166
 * @flags: some #XbNodeExportFlags, e.g. #XB_NODE_EXPORT_FLAG_NONE
1167
 * @error: the #GError, or %NULL
1168
 *
1169
 * Exports the node to XML.
1170
 *
1171
 * Returns: XML data, or %NULL for an error
1172
 *
1173
 * Since: 0.1.5
1174
 **/
1175
gchar *
1176
xb_builder_node_export(XbBuilderNode *self, XbNodeExportFlags flags, GError **error)
1177
169k
{
1178
169k
  g_autoptr(GString) xml = g_string_new(NULL);
1179
169k
  XbBuilderNodeExportHelper helper = {
1180
169k
      .flags = flags,
1181
169k
      .level = 0,
1182
169k
      .xml = xml,
1183
169k
  };
1184
169k
  g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL);
1185
169k
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1186
169k
  if ((flags & XB_NODE_EXPORT_FLAG_ADD_HEADER) > 0)
1187
0
    g_string_append(xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1188
169k
  if (!xb_builder_node_export_helper(self, &helper, error))
1189
0
    return NULL;
1190
169k
  return g_string_free(g_steal_pointer(&xml), FALSE);
1191
169k
}
1192
1193
/**
1194
 * xb_builder_node_add_token:
1195
 * @self: a #XbBuilderNode
1196
 * @token: a new token
1197
 *
1198
 * Adds a token to the builder node.
1199
 *
1200
 * Since: 0.3.1
1201
 **/
1202
void
1203
xb_builder_node_add_token(XbBuilderNode *self, const gchar *token)
1204
0
{
1205
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
1206
1207
0
  g_return_if_fail(self != NULL);
1208
0
  g_return_if_fail(token != NULL);
1209
1210
0
  if (priv->tokens == NULL)
1211
0
    priv->tokens = g_ptr_array_new_with_free_func(g_free);
1212
0
  g_ptr_array_add(priv->tokens, g_strdup(token));
1213
0
}
1214
1215
/**
1216
 * xb_builder_node_get_tokens:
1217
 * @self: a #XbBuilderNode
1218
 *
1219
 * Gets the tokens of the builder node.
1220
 *
1221
 * Returns: (transfer none) (element-type utf8) (nullable): tokens
1222
 *
1223
 * Since: 0.3.1
1224
 **/
1225
GPtrArray *
1226
xb_builder_node_get_tokens(XbBuilderNode *self)
1227
0
{
1228
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
1229
0
  g_return_val_if_fail(self != NULL, NULL);
1230
0
  return priv->tokens;
1231
0
}
1232
1233
/* private */
1234
void
1235
xb_builder_node_add_token_idx(XbBuilderNode *self, guint32 tail_idx)
1236
0
{
1237
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
1238
1239
0
  g_return_if_fail(self != NULL);
1240
0
  g_return_if_fail(tail_idx != XB_SILO_UNSET);
1241
1242
0
  if (priv->token_idxs == NULL)
1243
0
    priv->token_idxs = g_array_new(FALSE, FALSE, sizeof(guint32));
1244
0
  g_array_append_val(priv->token_idxs, tail_idx);
1245
0
}
1246
1247
/* Returns: (transfer none) (element-type guint32) (nullable): token indexes */
1248
GArray *
1249
xb_builder_node_get_token_idxs(XbBuilderNode *self)
1250
0
{
1251
0
  XbBuilderNodePrivate *priv = GET_PRIVATE(self);
1252
0
  g_return_val_if_fail(self != NULL, NULL);
1253
0
  return priv->token_idxs;
1254
0
}