Coverage Report

Created: 2025-10-13 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libxmlb/src/xb-node-query.c
Line
Count
Source
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 "XbNode"
8
9
#include "config.h"
10
11
#include <gio/gio.h>
12
#include <glib.h>
13
14
#include "xb-node-query.h"
15
#include "xb-node-silo.h"
16
#include "xb-silo-export-private.h"
17
#include "xb-silo-query-private.h"
18
19
/**
20
 * xb_node_query:
21
 * @self: a #XbNode
22
 * @xpath: an XPath, e.g. `id[abe.desktop]`
23
 * @limit: maximum number of results to return, or 0 for "all"
24
 * @error: the #GError, or %NULL
25
 *
26
 * Searches the silo using an XPath query, returning up to @limit results.
27
 *
28
 * It is safe to call this function from a different thread to the one that
29
 * created the #XbSilo.
30
 *
31
 * Please note: Only a subset of XPath is supported.
32
 *
33
 * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound
34
 *
35
 * Since: 0.1.0
36
 **/
37
GPtrArray *
38
xb_node_query(XbNode *self, const gchar *xpath, guint limit, GError **error)
39
0
{
40
0
  g_return_val_if_fail(XB_IS_NODE(self), NULL);
41
0
  g_return_val_if_fail(xpath != NULL, NULL);
42
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
43
0
  return xb_silo_query_with_root(xb_node_get_silo(self), self, xpath, limit, error);
44
0
}
45
46
/**
47
 * xb_node_query_full:
48
 * @self: a #XbNode
49
 * @query: an #XbQuery
50
 * @error: the #GError, or %NULL
51
 *
52
 * Searches the silo using a prepared query. To search using a query with
53
 * bound values, use xb_node_query_with_context().
54
 *
55
 * It is safe to call this function from a different thread to the one that
56
 * created the #XbSilo.
57
 *
58
 * Please note: Only a subset of XPath is supported.
59
 *
60
 * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound
61
 *
62
 * Since: 0.1.4
63
 **/
64
GPtrArray *
65
xb_node_query_full(XbNode *self, XbQuery *query, GError **error)
66
0
{
67
0
  g_return_val_if_fail(XB_IS_NODE(self), NULL);
68
0
  g_return_val_if_fail(XB_IS_QUERY(query), NULL);
69
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
70
0
  return xb_silo_query_with_root_full(xb_node_get_silo(self),
71
0
              self,
72
0
              query,
73
0
              NULL,
74
0
              FALSE,
75
0
              error);
76
0
}
77
78
/**
79
 * xb_node_query_with_context:
80
 * @self: a #XbNode
81
 * @query: an #XbQuery
82
 * @context: (nullable) (transfer none): context including values bound to opcodes of type
83
 *     %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if
84
 *     the query doesn’t need any context
85
 * @error: the #GError, or %NULL
86
 *
87
 * Searches the silo using a prepared query, substituting values from the
88
 * bindings in @context for bound opcodes as needed.
89
 *
90
 * It is safe to call this function from a different thread to the one that
91
 * created the #XbSilo.
92
 *
93
 * Please note: Only a subset of XPath is supported.
94
 *
95
 * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound
96
 *
97
 * Since: 0.3.0
98
 **/
99
GPtrArray *
100
xb_node_query_with_context(XbNode *self, XbQuery *query, XbQueryContext *context, GError **error)
101
0
{
102
0
  g_return_val_if_fail(XB_IS_NODE(self), NULL);
103
0
  g_return_val_if_fail(XB_IS_QUERY(query), NULL);
104
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
105
0
  return xb_silo_query_with_root_full(xb_node_get_silo(self),
106
0
              self,
107
0
              query,
108
0
              context,
109
0
              FALSE,
110
0
              error);
111
0
}
112
113
/**
114
 * xb_node_query_first_full:
115
 * @self: a #XbNode
116
 * @query: an #XbQuery
117
 * @error: the #GError, or %NULL
118
 *
119
 * Searches the silo using a prepared query, returning up to one result. To
120
 * search using a query with bound values, use
121
 * xb_node_query_first_with_context().
122
 *
123
 * It is safe to call this function from a different thread to the one that
124
 * created the #XbSilo.
125
 *
126
 * Please note: Only a subset of XPath is supported.
127
 *
128
 * Returns: (transfer full): a #XbNode, or %NULL if unfound
129
 *
130
 * Since: 0.1.11
131
 **/
132
XbNode *
133
xb_node_query_first_full(XbNode *self, XbQuery *query, GError **error)
134
0
{
135
0
  return xb_node_query_first_with_context(self, query, NULL, error);
136
0
}
137
138
/**
139
 * xb_node_query_first_with_context:
140
 * @self: a #XbNode
141
 * @query: an #XbQuery
142
 * @context: (nullable) (transfer none): context including values bound to opcodes of type
143
 *     %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if
144
 *     the query doesn’t need any context
145
 * @error: the #GError, or %NULL
146
 *
147
 * Searches the silo using a prepared query, returning up to one result.
148
 *
149
 * It is safe to call this function from a different thread to the one that
150
 * created the #XbSilo.
151
 *
152
 * Please note: Only a subset of XPath is supported.
153
 *
154
 * Returns: (transfer full): a #XbNode, or %NULL if unfound
155
 *
156
 * Since: 0.3.0
157
 **/
158
XbNode *
159
xb_node_query_first_with_context(XbNode *self,
160
         XbQuery *query,
161
         XbQueryContext *context,
162
         GError **error)
163
0
{
164
0
  g_autoptr(GPtrArray) results = NULL;
165
166
0
  g_return_val_if_fail(XB_IS_NODE(self), NULL);
167
0
  g_return_val_if_fail(XB_IS_QUERY(query), NULL);
168
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
169
170
  /* nodes don't have to include themselves as part of the query */
171
0
  results =
172
0
      xb_silo_query_with_root_full(xb_node_get_silo(self), self, query, context, TRUE, error);
173
174
0
  if (results == NULL)
175
0
    return NULL;
176
0
  return g_object_ref(g_ptr_array_index(results, 0));
177
0
}
178
179
/**
180
 * xb_node_query_first:
181
 * @self: a #XbNode
182
 * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
183
 * @error: the #GError, or %NULL
184
 *
185
 * Searches the node using an XPath query, returning up to one result.
186
 *
187
 * Please note: Only a tiny subset of XPath 1.0 is supported.
188
 *
189
 * Returns: (transfer full): a #XbNode, or %NULL if unfound
190
 *
191
 * Since: 0.1.0
192
 **/
193
XbNode *
194
xb_node_query_first(XbNode *self, const gchar *xpath, GError **error)
195
0
{
196
0
  g_autoptr(GPtrArray) results = NULL;
197
198
0
  g_return_val_if_fail(XB_IS_NODE(self), NULL);
199
0
  g_return_val_if_fail(xpath != NULL, NULL);
200
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
201
202
  /* nodes don't have to include themselves as part of the query */
203
0
  results = xb_silo_query_with_root(xb_node_get_silo(self), self, xpath, 1, error);
204
0
  if (results == NULL)
205
0
    return NULL;
206
0
  return g_object_ref(g_ptr_array_index(results, 0));
207
0
}
208
209
/**
210
 * xb_node_query_text:
211
 * @self: a #XbNode
212
 * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
213
 * @error: the #GError, or %NULL
214
 *
215
 * Searches the node using an XPath query, returning up to one result.
216
 *
217
 * It is safe to call this function from a different thread to the one that
218
 * created the #XbSilo.
219
 *
220
 * Please note: Only a subset of XPath is supported.
221
 *
222
 * Returns: (transfer none): a string, or %NULL if unfound
223
 *
224
 * Since: 0.1.0
225
 **/
226
const gchar *
227
xb_node_query_text(XbNode *self, const gchar *xpath, GError **error)
228
0
{
229
0
  XbSilo *silo;
230
0
  g_autoptr(GPtrArray) results = NULL;
231
0
  XbSiloNode *sn;
232
233
0
  g_return_val_if_fail(XB_IS_NODE(self), NULL);
234
0
  g_return_val_if_fail(xpath != NULL, NULL);
235
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
236
237
0
  silo = xb_node_get_silo(self);
238
0
  results = xb_silo_query_sn_with_root(silo, self, xpath, 1, error);
239
0
  if (results == NULL)
240
0
    return NULL;
241
0
  sn = g_ptr_array_index(results, 0);
242
0
  if (xb_silo_node_get_text_idx(sn) == XB_SILO_UNSET) {
243
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no text data");
244
0
    return NULL;
245
0
  }
246
0
  return xb_silo_from_strtab(silo, xb_silo_node_get_text_idx(sn), error);
247
0
}
248
249
/**
250
 * xb_node_query_attr:
251
 * @self: a #XbNode
252
 * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
253
 * @name: an attribute name, e.g. `type`
254
 * @error: the #GError, or %NULL
255
 *
256
 * Searches the node using an XPath query, returning up to one result.
257
 *
258
 * It is safe to call this function from a different thread to the one that
259
 * created the #XbSilo.
260
 *
261
 * Please note: Only a subset of XPath is supported.
262
 *
263
 * Returns: (transfer none): a string, or %NULL if unfound
264
 *
265
 * Since: 0.1.0
266
 **/
267
const gchar *
268
xb_node_query_attr(XbNode *self, const gchar *xpath, const gchar *name, GError **error)
269
0
{
270
0
  XbSiloNodeAttr *a;
271
0
  XbSilo *silo;
272
0
  g_autoptr(GPtrArray) results = NULL;
273
0
  XbSiloNode *sn;
274
275
0
  g_return_val_if_fail(XB_IS_NODE(self), NULL);
276
0
  g_return_val_if_fail(xpath != NULL, NULL);
277
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
278
279
0
  silo = xb_node_get_silo(self);
280
0
  results = xb_silo_query_sn_with_root(silo, self, xpath, 1, error);
281
0
  if (results == NULL)
282
0
    return NULL;
283
0
  sn = g_ptr_array_index(results, 0);
284
285
0
  a = xb_silo_get_node_attr_by_str(silo, sn, name);
286
0
  if (a == NULL) {
287
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no text data");
288
0
    return NULL;
289
0
  }
290
0
  return xb_silo_from_strtab(silo, a->attr_value, error);
291
0
}
292
293
/**
294
 * xb_node_query_export:
295
 * @self: a #XbNode
296
 * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
297
 * @error: the #GError, or %NULL
298
 *
299
 * Searches the node using an XPath query, returning an XML string of the
300
 * result and any children.
301
 *
302
 * It is safe to call this function from a different thread to the one that
303
 * created the #XbSilo.
304
 *
305
 * Please note: Only a subset of XPath is supported.
306
 *
307
 * Returns: (transfer none): a string, or %NULL if unfound
308
 *
309
 * Since: 0.1.0
310
 **/
311
gchar *
312
xb_node_query_export(XbNode *self, const gchar *xpath, GError **error)
313
0
{
314
0
  GString *xml;
315
0
  XbSilo *silo;
316
0
  g_autoptr(GPtrArray) results = NULL;
317
0
  XbSiloNode *sn;
318
319
0
  g_return_val_if_fail(XB_IS_NODE(self), NULL);
320
0
  g_return_val_if_fail(xpath != NULL, NULL);
321
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
322
323
0
  silo = xb_node_get_silo(self);
324
0
  results = xb_silo_query_sn_with_root(silo, self, xpath, 1, error);
325
0
  if (results == NULL)
326
0
    return NULL;
327
0
  sn = g_ptr_array_index(results, 0);
328
329
0
  xml = xb_silo_export_with_root(silo, sn, XB_NODE_EXPORT_FLAG_NONE, error);
330
0
  if (xml == NULL)
331
0
    return NULL;
332
0
  return g_string_free(xml, FALSE);
333
0
}
334
335
/**
336
 * xb_node_query_text_as_uint:
337
 * @self: a #XbNode
338
 * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
339
 * @error: the #GError, or %NULL
340
 *
341
 * Searches the node using an XPath query, returning up to one result.
342
 *
343
 * It is safe to call this function from a different thread to the one that
344
 * created the #XbSilo.
345
 *
346
 * Please note: Only a subset of XPath is supported.
347
 *
348
 * Returns: a #guint64, or %G_MAXUINT64 if unfound
349
 *
350
 * Since: 0.1.0
351
 **/
352
guint64
353
xb_node_query_text_as_uint(XbNode *self, const gchar *xpath, GError **error)
354
0
{
355
0
  const gchar *tmp;
356
357
0
  g_return_val_if_fail(XB_IS_NODE(self), G_MAXUINT64);
358
0
  g_return_val_if_fail(xpath != NULL, G_MAXUINT64);
359
0
  g_return_val_if_fail(error == NULL || *error == NULL, G_MAXUINT64);
360
361
0
  tmp = xb_node_query_text(self, xpath, error);
362
0
  if (tmp == NULL)
363
0
    return G_MAXUINT64;
364
365
0
  if (g_str_has_prefix(tmp, "0x"))
366
0
    return g_ascii_strtoull(tmp + 2, NULL, 16);
367
0
  return g_ascii_strtoull(tmp, NULL, 10);
368
0
}
369
370
/**
371
 * xb_node_query_attr_as_uint:
372
 * @self: a #XbNode
373
 * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
374
 * @name: an attribute name, e.g. `type`
375
 * @error: the #GError, or %NULL
376
 *
377
 * Searches the node using an XPath query, returning up to one result.
378
 *
379
 * It is safe to call this function from a different thread to the one that
380
 * created the #XbSilo.
381
 *
382
 * Please note: Only a subset of XPath is supported.
383
 *
384
 * Returns: a #guint64, or %G_MAXUINT64 if unfound
385
 *
386
 * Since: 0.1.0
387
 **/
388
guint64
389
xb_node_query_attr_as_uint(XbNode *self, const gchar *xpath, const gchar *name, GError **error)
390
0
{
391
0
  const gchar *tmp;
392
393
0
  g_return_val_if_fail(XB_IS_NODE(self), G_MAXUINT64);
394
0
  g_return_val_if_fail(xpath != NULL, G_MAXUINT64);
395
0
  g_return_val_if_fail(error == NULL || *error == NULL, G_MAXUINT64);
396
397
0
  tmp = xb_node_query_attr(self, xpath, name, error);
398
0
  if (tmp == NULL)
399
0
    return G_MAXUINT64;
400
401
0
  if (g_str_has_prefix(tmp, "0x"))
402
0
    return g_ascii_strtoull(tmp + 2, NULL, 16);
403
0
  return g_ascii_strtoull(tmp, NULL, 10);
404
0
}