Coverage Report

Created: 2025-08-29 06:48

/src/libxmlb/src/xb-silo-export.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
13
#include "xb-node-private.h"
14
#include "xb-silo-export-private.h"
15
#include "xb-silo-node.h"
16
#include "xb-string-private.h"
17
18
typedef struct {
19
  GString *xml;
20
  XbNodeExportFlags flags;
21
  guint32 off;
22
  guint level;
23
} XbSiloExportHelper;
24
25
static gboolean
26
xb_silo_export_node(XbSilo *self, XbSiloExportHelper *helper, XbSiloNode *sn, GError **error)
27
0
{
28
0
  XbSiloNode *sn2;
29
0
  const gchar *element_name;
30
31
0
  helper->off = xb_silo_get_offset_for_node(self, sn);
32
33
  /* add start of opening tag */
34
0
  if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) {
35
0
    for (guint i = 0; i < helper->level; i++)
36
0
      g_string_append(helper->xml, "  ");
37
0
  }
38
0
  element_name = xb_silo_from_strtab(self, sn->element_name, error);
39
0
  if (element_name == NULL)
40
0
    return FALSE;
41
0
  g_string_append_printf(helper->xml, "<%s", element_name);
42
43
  /* add any attributes */
44
0
  for (guint8 i = 0; i < xb_silo_node_get_attr_count(sn); i++) {
45
0
    XbSiloNodeAttr *a = xb_silo_node_get_attr(sn, i);
46
0
    const gchar *name_unsafe;
47
0
    const gchar *value_unsafe;
48
0
    g_autofree gchar *name = NULL;
49
0
    g_autofree gchar *value = NULL;
50
51
0
    name_unsafe = xb_silo_from_strtab(self, a->attr_name, error);
52
0
    if (name_unsafe == NULL)
53
0
      return FALSE;
54
0
    name = xb_string_xml_escape(name_unsafe);
55
56
0
    value_unsafe = xb_silo_from_strtab(self, a->attr_value, error);
57
0
    if (value_unsafe == NULL)
58
0
      return FALSE;
59
0
    value = xb_string_xml_escape(value_unsafe);
60
61
0
    g_string_append_printf(helper->xml, " %s=\"%s\"", name, value);
62
0
  }
63
64
  /* collapse open/close tags together if no text or children */
65
0
  if (helper->flags & XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY &&
66
0
      xb_silo_node_get_text_idx(sn) == XB_SILO_UNSET &&
67
0
      xb_silo_get_child_node(self, sn, NULL) == NULL) {
68
0
    g_string_append(helper->xml, " />");
69
70
    /* offset by opening tag and single byte sentinel */
71
0
    helper->off += xb_silo_node_get_size(sn);
72
0
    sn2 = xb_silo_get_node(self, helper->off, error);
73
0
    if (sn2 == NULL)
74
0
      return FALSE;
75
0
    helper->off += xb_silo_node_get_size(sn2);
76
0
  } else {
77
    /* finish the opening tag and add any text if it exists */
78
0
    if (xb_silo_node_get_text_idx(sn) != XB_SILO_UNSET) {
79
0
      const gchar *text_unsafe;
80
0
      g_autofree gchar *text = NULL;
81
82
0
      text_unsafe =
83
0
          xb_silo_from_strtab(self, xb_silo_node_get_text_idx(sn), error);
84
0
      if (text_unsafe == NULL)
85
0
        return FALSE;
86
0
      text = xb_string_xml_escape(text_unsafe);
87
0
      g_string_append(helper->xml, ">");
88
0
      g_string_append(helper->xml, text);
89
0
    } else {
90
0
      g_string_append(helper->xml, ">");
91
0
      if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE)
92
0
        g_string_append(helper->xml, "\n");
93
0
    }
94
0
    helper->off += xb_silo_node_get_size(sn);
95
96
    /* recurse deeper */
97
0
    while (TRUE) {
98
0
      XbSiloNode *child = xb_silo_get_node(self, helper->off, error);
99
0
      if (child == NULL)
100
0
        return FALSE;
101
0
      if (!xb_silo_node_has_flag(child, XB_SILO_NODE_FLAG_IS_ELEMENT))
102
0
        break;
103
0
      helper->level++;
104
0
      if (!xb_silo_export_node(self, helper, child, error))
105
0
        return FALSE;
106
0
      helper->level--;
107
0
    }
108
109
    /* check for the single byte sentinel */
110
0
    sn2 = xb_silo_get_node(self, helper->off, error);
111
0
    if (sn2 == NULL)
112
0
      return FALSE;
113
0
    if (xb_silo_node_has_flag(sn2, XB_SILO_NODE_FLAG_IS_ELEMENT)) {
114
0
      g_set_error(error,
115
0
            G_IO_ERROR,
116
0
            G_IO_ERROR_INVALID_DATA,
117
0
            "no seninel at %" G_GUINT32_FORMAT,
118
0
            helper->off);
119
0
      return FALSE;
120
0
    }
121
0
    helper->off += xb_silo_node_get_size(sn2);
122
123
    /* add closing tag */
124
0
    if ((helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) > 0 &&
125
0
        xb_silo_node_get_text_idx(sn) == XB_SILO_UNSET) {
126
0
      for (guint i = 0; i < helper->level; i++)
127
0
        g_string_append(helper->xml, "  ");
128
0
    }
129
0
    g_string_append_printf(helper->xml, "</%s>", element_name);
130
0
  }
131
132
  /* add any optional tail */
133
0
  if (xb_silo_node_get_tail_idx(sn) != XB_SILO_UNSET) {
134
0
    const gchar *tail_unsafe;
135
0
    g_autofree gchar *tail = NULL;
136
137
0
    tail_unsafe = xb_silo_from_strtab(self, xb_silo_node_get_tail_idx(sn), error);
138
0
    if (tail_unsafe == NULL)
139
0
      return FALSE;
140
0
    tail = xb_string_xml_escape(tail_unsafe);
141
0
    g_string_append(helper->xml, tail);
142
0
  }
143
144
0
  if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE)
145
0
    g_string_append(helper->xml, "\n");
146
147
0
  return TRUE;
148
0
}
149
150
/* private */
151
GString *
152
xb_silo_export_with_root(XbSilo *self, XbSiloNode *sroot, XbNodeExportFlags flags, GError **error)
153
0
{
154
0
  XbSiloNode *sn;
155
0
  XbSiloExportHelper helper = {
156
0
      .flags = flags,
157
0
      .level = 0,
158
0
      .off = sizeof(XbSiloHeader),
159
0
  };
160
161
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
162
163
  /* this implies the other */
164
0
  if (flags & XB_NODE_EXPORT_FLAG_ONLY_CHILDREN)
165
0
    flags |= XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS;
166
167
  /* optional subtree export */
168
0
  if (sroot != NULL) {
169
0
    sn = sroot;
170
0
    if (sn != NULL && flags & XB_NODE_EXPORT_FLAG_ONLY_CHILDREN) {
171
0
      g_autoptr(GError) error_local = NULL;
172
0
      sn = xb_silo_get_child_node(self, sn, &error_local);
173
0
      if (sn == NULL) {
174
0
        if (!g_error_matches(error_local,
175
0
                 G_IO_ERROR,
176
0
                 G_IO_ERROR_INVALID_ARGUMENT)) {
177
0
          g_propagate_error(error, g_steal_pointer(&error_local));
178
0
          return NULL;
179
0
        }
180
0
      }
181
0
    }
182
0
  } else {
183
0
    sn = xb_silo_get_root_node(self, error);
184
0
    if (sn == NULL)
185
0
      return NULL;
186
0
  }
187
188
  /* no root */
189
0
  if (sn == NULL) {
190
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no data to export");
191
0
    return NULL;
192
0
  }
193
194
  /* root node */
195
0
  helper.xml = g_string_new(NULL);
196
0
  if ((flags & XB_NODE_EXPORT_FLAG_ADD_HEADER) > 0)
197
0
    g_string_append(helper.xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
198
0
  do {
199
0
    g_autoptr(GError) error_local = NULL;
200
0
    if (!xb_silo_export_node(self, &helper, sn, error)) {
201
0
      g_string_free(helper.xml, TRUE);
202
0
      return NULL;
203
0
    }
204
0
    if ((flags & XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS) == 0)
205
0
      break;
206
0
    sn = xb_silo_get_next_node(self, sn, &error_local);
207
0
    if (sn == NULL) {
208
0
      if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
209
0
        break;
210
0
      g_propagate_error(error, g_steal_pointer(&error_local));
211
0
      return NULL;
212
0
    }
213
0
  } while (TRUE);
214
215
  /* success */
216
0
  return helper.xml;
217
0
}
218
219
/**
220
 * xb_silo_export:
221
 * @self: a #XbSilo
222
 * @flags: some #XbNodeExportFlags, e.g. #XB_NODE_EXPORT_FLAG_NONE
223
 * @error: the #GError, or %NULL
224
 *
225
 * Exports the silo back to XML.
226
 *
227
 * Returns: XML data, or %NULL for an error
228
 *
229
 * Since: 0.1.0
230
 **/
231
gchar *
232
xb_silo_export(XbSilo *self, XbNodeExportFlags flags, GError **error)
233
0
{
234
0
  GString *xml;
235
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
236
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
237
0
  xml = xb_silo_export_with_root(self, NULL, flags, error);
238
0
  if (xml == NULL)
239
0
    return NULL;
240
0
  return g_string_free(xml, FALSE);
241
0
}
242
243
/**
244
 * xb_silo_export_file:
245
 * @self: a #XbSilo
246
 * @file: a #GFile
247
 * @flags: some #XbNodeExportFlags, e.g. #XB_NODE_EXPORT_FLAG_NONE
248
 * @cancellable: a #GCancellable, or %NULL
249
 * @error: the #GError, or %NULL
250
 *
251
 * Exports the silo back to an XML file.
252
 *
253
 * Returns: %TRUE on success
254
 *
255
 * Since: 0.1.2
256
 **/
257
gboolean
258
xb_silo_export_file(XbSilo *self,
259
        GFile *file,
260
        XbNodeExportFlags flags,
261
        GCancellable *cancellable,
262
        GError **error)
263
0
{
264
0
  g_autoptr(GString) xml = NULL;
265
266
0
  g_return_val_if_fail(XB_IS_SILO(self), FALSE);
267
0
  g_return_val_if_fail(G_IS_FILE(file), FALSE);
268
0
  g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
269
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
270
271
0
  xml = xb_silo_export_with_root(self, NULL, flags, error);
272
0
  if (xml == NULL)
273
0
    return FALSE;
274
0
  return g_file_replace_contents(file,
275
0
               xml->str,
276
0
               xml->len,
277
0
               NULL,  /* etag */
278
0
               FALSE, /* make-backup */
279
0
               G_FILE_CREATE_NONE,
280
0
               NULL, /* new etag */
281
0
               cancellable,
282
0
               error);
283
0
}