/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 | } |