1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2014 Richard Hughes <richard@hughsie.com>
4 *
5 * SPDX-License-Identifier: LGPL-2.1+
6 */
7
8 /**
9 * SECTION:as-node
10 * @short_description: A simple DOM parser
11 * @include: appstream-glib.h
12 * @stability: Stable
13 *
14 * These helper functions allow parsing to and from #AsApp's and the AppStream
15 * XML representation. This parser is UTF-8 safe, but not very fast, and parsers
16 * like expat should be used if full XML specification adherence is required.
17 *
18 * See also: #AsApp
19 */
20
21 #include "config.h"
22
23 #include <glib.h>
24 #include <string.h>
25
26 #include "as-markup.h"
27 #include "as-node-private.h"
28 #include "as-ref-string.h"
29 #include "as-utils-private.h"
30
31 typedef struct {
32 GHashTable *intern_attr; /* key=value of AsRefString */
33 GHashTable *intern_name; /* key=value of AsRefString */
34 GHashTable *intern_lang; /* key=value of AsRefString */
35 } AsNodeRoot;
36
37 typedef struct
38 {
39 GList *attrs;
40 union {
41 AsTag tag;
42 const gchar *name_const; /* only if is_name_const = TRUE */
43 AsRefString *name; /* only if is_tag_valid = FALSE */
44 };
45 union {
46 AsNodeRoot *root; /* only if is_root_node = TRUE */
47 const gchar *cdata_const; /* only if is_cdata_const = TRUE */
48 AsRefString *cdata;
49 };
50 guint8 is_root_node:1;
51 guint8 is_cdata_const:1;
52 guint8 is_name_const:1;
53 guint8 is_cdata_escaped:1;
54 guint8 is_cdata_ignore:1;
55 guint8 is_tag_valid:1;
56 } AsNodeData;
57
58 typedef struct {
59 AsRefString *key;
60 AsRefString *value;
61 } AsNodeAttr;
62
63 /**
64 * as_node_new: (skip)
65 *
66 * Creates a new empty tree whicah can have nodes appended to it.
67 *
68 * Returns: (transfer full): a new empty tree
69 *
70 * Since: 0.1.0
71 **/
72 AsNode *
as_node_new(void)73 as_node_new (void)
74 {
75 AsNodeData *data;
76 data = g_slice_new0 (AsNodeData);
77 data->tag = AS_TAG_LAST;
78 data->is_tag_valid = TRUE;
79 data->is_root_node = TRUE;
80 data->root = g_new0 (AsNodeRoot, 1);
81 data->root->intern_attr = g_hash_table_new_full (g_str_hash,
82 g_str_equal,
83 (GDestroyNotify) as_ref_string_unref,
84 NULL);
85 data->root->intern_name = g_hash_table_new_full (g_str_hash,
86 g_str_equal,
87 (GDestroyNotify) as_ref_string_unref,
88 NULL);
89 data->root->intern_lang = g_hash_table_new_full (g_str_hash,
90 g_str_equal,
91 (GDestroyNotify) as_ref_string_unref,
92 NULL);
93 return g_node_new (data);
94 }
95
96 static void
as_node_attr_free(AsNodeAttr * attr)97 as_node_attr_free (AsNodeAttr *attr)
98 {
99 g_slice_free (AsNodeAttr, attr);
100 }
101
102 /* transfer: none */
103 static AsRefString *
as_node_intern(GHashTable * hash,const gchar * key)104 as_node_intern (GHashTable *hash, const gchar *key)
105 {
106 AsRefString *rstr = g_hash_table_lookup (hash, key);
107 if (rstr == NULL) {
108 rstr = as_ref_string_new (key);
109 g_hash_table_add (hash, rstr);
110 }
111 return rstr;
112 }
113
114 static AsNodeAttr *
as_node_attr_insert(AsNode * root,AsNodeData * data,const gchar * key,const gchar * value)115 as_node_attr_insert (AsNode *root,
116 AsNodeData *data,
117 const gchar *key,
118 const gchar *value)
119 {
120 AsNodeAttr *attr;
121 AsNodeRoot *root_data = ((AsNodeData *)root->data)->root;
122
123 attr = g_slice_new0 (AsNodeAttr);
124 attr->key = as_node_intern (root_data->intern_attr, key);
125 attr->value = as_node_intern (root_data->intern_attr, value);
126 data->attrs = g_list_prepend (data->attrs, attr);
127 return attr;
128 }
129
130 static AsNodeAttr *
as_node_attr_find(AsNodeData * data,const gchar * key)131 as_node_attr_find (AsNodeData *data, const gchar *key)
132 {
133 AsNodeAttr *attr;
134 GList *l;
135
136 for (l = data->attrs; l != NULL; l = l->next) {
137 attr = l->data;
138 if (g_strcmp0 (attr->key, key) == 0)
139 return attr;
140 }
141 return NULL;
142 }
143
144 static AsRefString *
as_node_attr_lookup(AsNodeData * data,const gchar * key)145 as_node_attr_lookup (AsNodeData *data, const gchar *key)
146 {
147 AsNodeAttr *attr;
148 attr = as_node_attr_find (data, key);
149 if (attr != NULL)
150 return attr->value;
151 return NULL;
152 }
153
154 static gboolean
as_node_destroy_node_cb(AsNode * node,gpointer user_data)155 as_node_destroy_node_cb (AsNode *node, gpointer user_data)
156 {
157 AsNodeData *data = node->data;
158 if (data == NULL)
159 return FALSE;
160 if (!data->is_tag_valid && !data->is_name_const && data->name != NULL)
161 as_ref_string_unref (data->name);
162 if (data->is_root_node) {
163 g_hash_table_unref (data->root->intern_attr);
164 g_hash_table_unref (data->root->intern_name);
165 g_hash_table_unref (data->root->intern_lang);
166 g_free (data->root);
167 } else {
168 if (!data->is_cdata_const && data->cdata != NULL)
169 as_ref_string_unref (data->cdata);
170 }
171 g_list_free_full (data->attrs, (GDestroyNotify) as_node_attr_free);
172 g_slice_free (AsNodeData, data);
173 return FALSE;
174 }
175
176 /**
177 * as_node_unref:
178 * @node: a #AsNode.
179 *
180 * Deallocates all notes in the tree.
181 *
182 * Since: 0.1.0
183 **/
184 void
as_node_unref(AsNode * node)185 as_node_unref (AsNode *node)
186 {
187 g_node_traverse (node,
188 G_PRE_ORDER,
189 G_TRAVERSE_ALL,
190 -1,
191 as_node_destroy_node_cb,
192 NULL);
193 g_node_destroy (node);
194 }
195
196 /**
197 * as_node_error_quark:
198 *
199 * Return value: An error quark.
200 *
201 * Since: 0.1.0
202 **/
203 G_DEFINE_QUARK (as-node-error-quark, as_node_error)
204
205 static void
as_node_string_replace_inplace(gchar * text,const gchar * search,gchar replace)206 as_node_string_replace_inplace (gchar *text,
207 const gchar *search,
208 gchar replace)
209 {
210 const gchar *start = text;
211 gchar *tmp;
212 gsize len;
213 gsize len_escaped = 0;
214
215 while ((tmp = g_strstr_len (start, -1, search)) != NULL) {
216 *tmp = replace;
217 len = (guint) strlen (tmp);
218 if (len_escaped == 0)
219 len_escaped = (guint) strlen (search);
220 memcpy (tmp + 1,
221 tmp + len_escaped,
222 (len - len_escaped) + 1);
223 start = tmp + 1;
224 }
225 }
226
227 static void
as_node_cdata_to_heap(AsNodeData * data)228 as_node_cdata_to_heap (AsNodeData *data)
229 {
230 if (!data->is_cdata_const)
231 return;
232 data->cdata = as_ref_string_new (data->cdata);
233 data->is_cdata_const = FALSE;
234 }
235
236 static void
as_node_cdata_to_intern(AsNode * root,AsNodeData * data)237 as_node_cdata_to_intern (AsNode *root, AsNodeData *data)
238 {
239 AsNodeRoot *root_data = ((AsNodeData *)root->data)->root;
240 AsRefString *tmp;
241 if (data->is_cdata_const)
242 return;
243 tmp = as_node_intern (root_data->intern_attr, data->cdata);
244 as_ref_string_unref (data->cdata);
245 data->cdata_const = tmp;
246 data->is_cdata_const = TRUE;
247 }
248
249 static void
as_node_cdata_to_raw(AsNodeData * data)250 as_node_cdata_to_raw (AsNodeData *data)
251 {
252 if (data->is_root_node)
253 return;
254 if (!data->is_cdata_escaped)
255 return;
256 if (data->cdata == NULL)
257 return;
258 if (data->is_cdata_const)
259 as_node_cdata_to_heap (data);
260 as_node_string_replace_inplace (data->cdata, "&", '&');
261 as_node_string_replace_inplace (data->cdata, "<", '<');
262 as_node_string_replace_inplace (data->cdata, ">", '>');
263 data->is_cdata_escaped = FALSE;
264 }
265
266 static void
as_node_cdata_to_escaped(AsNodeData * data)267 as_node_cdata_to_escaped (AsNodeData *data)
268 {
269 if (data->is_root_node)
270 return;
271 if (data->is_cdata_escaped)
272 return;
273 if (data->cdata == NULL)
274 return;
275 if (g_strstr_len (data->cdata, -1, "&") != NULL ||
276 g_strstr_len (data->cdata, -1, "<") != NULL ||
277 g_strstr_len (data->cdata, -1, ">") != NULL) {
278 g_autoptr(GString) str = g_string_new (data->cdata);
279 as_ref_string_unref (data->cdata);
280 as_utils_string_replace (str, "&", "&");
281 as_utils_string_replace (str, "<", "<");
282 as_utils_string_replace (str, ">", ">");
283 data->cdata = as_ref_string_new_with_length (str->str, str->len);
284 }
285 data->is_cdata_escaped = TRUE;
286 }
287
288 static void
as_node_add_padding(GString * xml,guint depth)289 as_node_add_padding (GString *xml, guint depth)
290 {
291 guint i;
292 for (i = 0; i < depth; i++)
293 g_string_append (xml, " ");
294 }
295
296 static gint
as_node_sort_attr_by_name_cb(gconstpointer a,gconstpointer b)297 as_node_sort_attr_by_name_cb (gconstpointer a, gconstpointer b)
298 {
299 AsNodeAttr *attr1 = (AsNodeAttr *) a;
300 AsNodeAttr *attr2 = (AsNodeAttr *) b;
301
302 /* this is always first */
303 if (g_strcmp0 (attr1->key, "type") == 0)
304 return -1;
305 if (g_strcmp0 (attr2->key, "type") == 0)
306 return 1;
307
308 return g_strcmp0 (attr1->key, attr2->key);
309 }
310
311 static gchar *
as_node_get_attr_string(AsNodeData * data)312 as_node_get_attr_string (AsNodeData *data)
313 {
314 AsNodeAttr *attr;
315 GList *l;
316 GString *str;
317
318 /* ensure predictable output order */
319 data->attrs = g_list_sort (data->attrs, as_node_sort_attr_by_name_cb);
320
321 str = g_string_new ("");
322 for (l = data->attrs; l != NULL; l = l->next) {
323 g_autoptr(GString) value_safe = NULL;
324 attr = l->data;
325 if (g_strcmp0 (attr->key, "@comment") == 0 ||
326 g_strcmp0 (attr->key, "@comment-tmp") == 0)
327 continue;
328 value_safe = g_string_new (attr->value);
329 as_utils_string_replace (value_safe, "&", "&");
330 as_utils_string_replace (value_safe, "<", "<");
331 as_utils_string_replace (value_safe, ">", ">");
332 as_utils_string_replace (value_safe, "\"", """);
333 as_utils_string_replace (value_safe, "'", "'");
334 g_string_append_printf (str, " %s=\"%s\"",
335 attr->key, value_safe->str);
336 }
337 return g_string_free (str, FALSE);
338 }
339
340 static const gchar *
as_tag_data_get_name(AsNodeData * data)341 as_tag_data_get_name (AsNodeData *data)
342 {
343 if (data->is_tag_valid)
344 return as_tag_to_string (data->tag);
345 return data->name;
346 }
347
348 static void
as_node_data_set_name(AsNode * root,AsNodeData * data,const gchar * name,AsNodeInsertFlags flags)349 as_node_data_set_name (AsNode *root,
350 AsNodeData *data,
351 const gchar *name,
352 AsNodeInsertFlags flags)
353 {
354 if ((flags & AS_NODE_INSERT_FLAG_MARK_TRANSLATABLE) == 0) {
355 /* only store the name if the tag is not recognised */
356 AsTag tag = as_tag_from_string (name);
357 if (tag == AS_TAG_UNKNOWN) {
358 AsNodeRoot *root_data = ((AsNodeData *)root->data)->root;
359 data->name_const = as_node_intern (root_data->intern_name, name);
360 data->is_name_const = TRUE;
361 data->is_tag_valid = FALSE;
362 } else {
363 data->name = NULL;
364 data->tag = tag;
365 data->is_tag_valid = TRUE;
366 }
367 } else {
368 /* always store the translated tag */
369 g_autofree gchar *name_tmp = g_strdup_printf ("_%s", name);
370 data->name = as_ref_string_new (name_tmp);
371 data->is_tag_valid = FALSE;
372 }
373 }
374
375 static void
as_node_sort_children(AsNode * first)376 as_node_sort_children (AsNode *first)
377 {
378 AsNodeData *d1;
379 AsNodeData *d2;
380 AsNode *child;
381 gpointer tmp;
382
383 d1 = (AsNodeData *) first->data;
384 for (child = first->next; child != NULL; child = child->next) {
385 d2 = (AsNodeData *) child->data;
386 if (g_strcmp0 (as_tag_data_get_name (d1),
387 as_tag_data_get_name (d2)) > 0) {
388 tmp = child->data;
389 child->data = first->data;
390 first->data = tmp;
391 tmp = child->children;
392 child->children = first->children;
393 first->children = tmp;
394 as_node_sort_children (first);
395 }
396 }
397 if (first->next != NULL)
398 as_node_sort_children (first->next);
399 }
400
401 static void
as_node_to_xml_string(GString * xml,guint depth_offset,const AsNode * n,AsNodeToXmlFlags flags)402 as_node_to_xml_string (GString *xml,
403 guint depth_offset,
404 const AsNode *n,
405 AsNodeToXmlFlags flags)
406 {
407 AsNodeData *data = n->data;
408 AsNode *c;
409 const gchar *tag_str;
410 const gchar *comment;
411 guint depth = g_node_depth ((GNode *) n);
412 gchar *attrs;
413
414 /* comment */
415 comment = as_node_get_comment (n);
416 if (comment != NULL) {
417 guint i;
418 g_auto(GStrv) split = NULL;
419
420 /* do not put additional spacing for the root node */
421 if (depth_offset < g_node_depth ((GNode *) n) &&
422 (flags & AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE) > 0)
423 g_string_append (xml, "\n");
424 if ((flags & AS_NODE_TO_XML_FLAG_FORMAT_INDENT) > 0)
425 as_node_add_padding (xml, depth - depth_offset);
426
427 /* add each comment section */
428 split = g_strsplit (comment, "<&>", -1);
429 for (i = 0; split[i] != NULL; i++) {
430 g_string_append_printf (xml, "<!--%s-->", split[i]);
431 if ((flags & AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE) > 0)
432 g_string_append (xml, "\n");
433 }
434 }
435
436 /* root node */
437 if (data == NULL || as_node_get_tag (n) == AS_TAG_LAST) {
438 if ((flags & AS_NODE_TO_XML_FLAG_SORT_CHILDREN) > 0)
439 as_node_sort_children (n->children);
440 for (c = n->children; c != NULL; c = c->next)
441 as_node_to_xml_string (xml, depth_offset, c, flags);
442
443 /* leaf node */
444 } else if (n->children == NULL) {
445 if ((flags & AS_NODE_TO_XML_FLAG_FORMAT_INDENT) > 0)
446 as_node_add_padding (xml, depth - depth_offset);
447 tag_str = as_tag_data_get_name (data);
448 if (tag_str == NULL)
449 return;
450 attrs = as_node_get_attr_string (data);
451 if (data->cdata == NULL || data->cdata[0] == '\0') {
452 g_string_append_printf (xml, "<%s%s/>",
453 tag_str, attrs);
454 } else {
455 as_node_cdata_to_escaped (data);
456 g_string_append_printf (xml, "<%s%s>%s</%s>",
457 tag_str,
458 attrs,
459 data->cdata,
460 tag_str);
461 }
462 if ((flags & AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE) > 0)
463 g_string_append (xml, "\n");
464 g_free (attrs);
465
466 /* node with children */
467 } else {
468 if ((flags & AS_NODE_TO_XML_FLAG_FORMAT_INDENT) > 0)
469 as_node_add_padding (xml, depth - depth_offset);
470 attrs = as_node_get_attr_string (data);
471 tag_str = as_tag_data_get_name (data);
472 g_string_append_printf (xml, "<%s%s>", tag_str, attrs);
473 if ((flags & AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE) > 0)
474 g_string_append (xml, "\n");
475 g_free (attrs);
476 if ((flags & AS_NODE_TO_XML_FLAG_SORT_CHILDREN) > 0)
477 as_node_sort_children (n->children);
478 for (c = n->children; c != NULL; c = c->next)
479 as_node_to_xml_string (xml, depth_offset, c, flags);
480
481 if ((flags & AS_NODE_TO_XML_FLAG_FORMAT_INDENT) > 0)
482 as_node_add_padding (xml, depth - depth_offset);
483 g_string_append_printf (xml, "</%s>", tag_str);
484 if ((flags & AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE) > 0)
485 g_string_append (xml, "\n");
486 }
487 }
488
489 /**
490 * as_node_reflow_text:
491 * @text: XML text data
492 * @text_len: length of @text
493 *
494 * Converts pretty-formatted source text into a format suitable for AppStream.
495 * This might include joining paragraphs, suppressing newlines or doing other
496 * sanity checks to the text.
497 *
498 * Returns: (transfer full): a new string
499 *
500 * Since: 0.1.4
501 **/
502 AsRefString *
as_node_reflow_text(const gchar * text,gssize text_len)503 as_node_reflow_text (const gchar *text, gssize text_len)
504 {
505 guint i;
506 guint newline_count = 0;
507 g_auto(GStrv) split = NULL;
508 g_autoptr(GString) tmp = NULL;
509
510 /* all on one line, no trailing or leading whitespace */
511 if (g_strstr_len (text, text_len, "\n") == NULL &&
512 !g_str_has_prefix (text, " ") &&
513 !g_str_has_suffix (text, " ")) {
514 gsize len;
515 len = text_len >= 0 ? (gsize) text_len : strlen (text);
516 return as_ref_string_new_with_length (text, len);
517 }
518
519 /* split the text into lines */
520 tmp = g_string_sized_new ((gsize) text_len + 1);
521 split = g_strsplit (text, "\n", -1);
522 for (i = 0; split[i] != NULL; i++) {
523
524 /* remove leading and trailing whitespace */
525 g_strstrip (split[i]);
526
527 /* if this is a blank line we end the paragraph mode
528 * and swallow the newline. If we see exactly two
529 * newlines in sequence then do a paragraph break */
530 if (split[i][0] == '\0') {
531 newline_count++;
532 continue;
533 }
534
535 /* if the line just before this one was not a newline
536 * then seporate the words with a space */
537 if (newline_count == 1 && tmp->len > 0)
538 g_string_append (tmp, " ");
539
540 /* if we had more than one newline in sequence add a paragraph
541 * break */
542 if (newline_count > 1)
543 g_string_append (tmp, "\n\n");
544
545 /* add the actual stripped text */
546 g_string_append (tmp, split[i]);
547
548 /* this last section was paragraph */
549 newline_count = 1;
550 }
551 return as_ref_string_new_with_length (tmp->str, tmp->len);
552 }
553
554 typedef struct {
555 AsNode *current;
556 AsNodeFromXmlFlags flags;
557 const gchar * const *locales;
558 } AsNodeToXmlHelper;
559
560 /**
561 * as_node_to_xml:
562 * @node: a #AsNode.
563 * @flags: the AsNodeToXmlFlags, e.g. %AS_NODE_TO_XML_FLAG_NONE.
564 *
565 * Converts a node and it's children to XML.
566 *
567 * Returns: (transfer full): a #GString
568 *
569 * Since: 0.1.0
570 **/
571 GString *
as_node_to_xml(const AsNode * node,AsNodeToXmlFlags flags)572 as_node_to_xml (const AsNode *node, AsNodeToXmlFlags flags)
573 {
574 GString *xml;
575 const AsNode *l;
576 guint depth_offset;
577
578 g_return_val_if_fail (node != NULL, NULL);
579
580 depth_offset = g_node_depth ((GNode *) node) + 1;
581 xml = g_string_new ("");
582 if ((flags & AS_NODE_TO_XML_FLAG_ADD_HEADER) > 0)
583 g_string_append (xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
584 if ((flags & AS_NODE_TO_XML_FLAG_INCLUDE_SIBLINGS) > 0) {
585 for (l = node; l != NULL; l = l->next)
586 as_node_to_xml_string (xml, depth_offset, l, flags);
587 } else {
588 as_node_to_xml_string (xml, depth_offset, node, flags);
589 }
590 return xml;
591 }
592
593 static void
as_node_start_element_cb(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)594 as_node_start_element_cb (GMarkupParseContext *context,
595 const gchar *element_name,
596 const gchar **attribute_names,
597 const gchar **attribute_values,
598 gpointer user_data,
599 GError **error)
600 {
601 AsNodeToXmlHelper *helper = (AsNodeToXmlHelper *) user_data;
602 AsNodeData *data;
603 AsNodeData *data_parent;
604 AsNode *current;
605 guint i;
606
607 /* check if we should ignore the locale */
608 data = g_slice_new0 (AsNodeData);
609
610 /* parent node is being ignored */
611 data_parent = helper->current->data;
612 if (data_parent->is_cdata_ignore)
613 data->is_cdata_ignore = TRUE;
614
615 /* check if we should ignore the locale */
616 if (!data->is_cdata_ignore &&
617 helper->flags & AS_NODE_FROM_XML_FLAG_ONLY_NATIVE_LANGS) {
618 for (i = 0; attribute_names[i] != NULL; i++) {
619 if (g_strcmp0 (attribute_names[i], "xml:lang") == 0) {
620 const gchar *lang = attribute_values[i];
621 if (lang != NULL && !g_strv_contains (helper->locales, lang))
622 data->is_cdata_ignore = TRUE;
623 }
624 }
625 }
626
627 /* create the new node data */
628 if (!data->is_cdata_ignore) {
629 AsNode *root = g_node_get_root (helper->current);
630 as_node_data_set_name (root,
631 data,
632 element_name,
633 AS_NODE_INSERT_FLAG_NONE);
634 for (i = 0; attribute_names[i] != NULL; i++) {
635 as_node_attr_insert (root, data,
636 attribute_names[i],
637 attribute_values[i]);
638 }
639 }
640
641 /* add the node to the DOM */
642 current = g_node_append_data (helper->current, data);
643
644 /* transfer the ownership of the comment to the new child */
645 if (helper->flags & AS_NODE_FROM_XML_FLAG_KEEP_COMMENTS) {
646 AsRefString *tmp;
647 tmp = as_node_get_attribute_as_refstr (helper->current, "@comment-tmp");
648 if (tmp != NULL) {
649 as_node_add_attribute (current, "@comment", tmp);
650 as_node_remove_attribute (helper->current, "@comment-tmp");
651 }
652 }
653
654 /* the child is now the node to be processed */
655 helper->current = current;
656 }
657
658 static void
as_node_end_element_cb(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)659 as_node_end_element_cb (GMarkupParseContext *context,
660 const gchar *element_name,
661 gpointer user_data,
662 GError **error)
663 {
664 AsNodeToXmlHelper *helper = (AsNodeToXmlHelper *) user_data;
665 helper->current = helper->current->parent;
666 }
667
668 static void
as_node_text_cb(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)669 as_node_text_cb (GMarkupParseContext *context,
670 const gchar *text,
671 gsize text_len,
672 gpointer user_data,
673 GError **error)
674 {
675 AsNodeToXmlHelper *helper = (AsNodeToXmlHelper *) user_data;
676 AsNodeData *data;
677 guint i;
678
679 /* no data */
680 if (text_len == 0)
681 return;
682
683 /* ignoring */
684 data = helper->current->data;
685 if (data->is_cdata_ignore)
686 return;
687
688 /* all whitespace? */
689 for (i = 0; i < text_len; i++) {
690 if (!g_ascii_isspace(text[i]))
691 break;
692 }
693 if (i >= text_len)
694 return;
695
696 /* split up into lines and add each with spaces stripped */
697 if (data->cdata != NULL) {
698 g_set_error (error,
699 AS_NODE_ERROR,
700 AS_NODE_ERROR_INVALID_MARKUP,
701 "<%s> already set '%s' and tried to replace with '%s'",
702 as_tag_data_get_name (data),
703 data->cdata, text);
704 return;
705 }
706 if ((helper->flags & AS_NODE_FROM_XML_FLAG_LITERAL_TEXT) > 0) {
707 data->cdata = as_ref_string_new_with_length (text, text_len + 1);
708 } else {
709 data->cdata = as_node_reflow_text (text, (gssize) text_len);
710 }
711
712 /* intern commonly duplicated tag values and save a bit of memory */
713 if (data->is_tag_valid && data->cdata != NULL) {
714 AsNode *root = g_node_get_root (helper->current);
715 switch (data->tag) {
716 case AS_TAG_CATEGORY:
717 case AS_TAG_COMPULSORY_FOR_DESKTOP:
718 case AS_TAG_CONTENT_ATTRIBUTE:
719 case AS_TAG_DEVELOPER_NAME:
720 case AS_TAG_EXTENDS:
721 case AS_TAG_ICON:
722 case AS_TAG_ID:
723 case AS_TAG_KUDO:
724 case AS_TAG_LANG:
725 case AS_TAG_METADATA_LICENSE:
726 case AS_TAG_MIMETYPE:
727 case AS_TAG_PROJECT_GROUP:
728 case AS_TAG_PROJECT_LICENSE:
729 case AS_TAG_SOURCE_PKGNAME:
730 case AS_TAG_URL:
731 as_node_cdata_to_intern (root, data);
732 break;
733 default:
734 break;
735 }
736 }
737 }
738
739 static void
as_node_passthrough_cb(GMarkupParseContext * context,const gchar * passthrough_text,gsize passthrough_len,gpointer user_data,GError ** error)740 as_node_passthrough_cb (GMarkupParseContext *context,
741 const gchar *passthrough_text,
742 gsize passthrough_len,
743 gpointer user_data,
744 GError **error)
745 {
746 AsNodeToXmlHelper *helper = (AsNodeToXmlHelper *) user_data;
747 const gchar *existing;
748 const gchar *tmp;
749 gchar *found;
750 g_autofree gchar *text = NULL;
751
752 /* only keep comments when told to */
753 if ((helper->flags & AS_NODE_FROM_XML_FLAG_KEEP_COMMENTS) == 0)
754 return;
755
756 /* xml header */
757 if (g_strstr_len (passthrough_text,
758 (gssize) passthrough_len,
759 "<?xml") != NULL)
760 return;
761
762 /* get stripped comment without '<!--' and '-->' */
763 text = g_strndup (passthrough_text, passthrough_len);
764 if (!g_str_has_prefix (text, "<!--")) {
765 g_warning ("Unexpected input: %s", text);
766 return;
767 }
768 found = g_strrstr (text, "-->");
769 if (found != NULL)
770 *found = '\0';
771 tmp = text + 4;
772 if ((helper->flags & AS_NODE_FROM_XML_FLAG_LITERAL_TEXT) == 0)
773 tmp = g_strstrip ((gchar *) tmp);
774 if (tmp == NULL || tmp[0] == '\0')
775 return;
776
777 /* append together comments */
778 existing = as_node_get_attribute (helper->current, "@comment-tmp");
779 if (existing == NULL) {
780 as_node_add_attribute (helper->current, "@comment-tmp", tmp);
781 } else {
782 g_autofree gchar *join = NULL;
783 join = g_strdup_printf ("%s<&>%s", existing, tmp);
784 as_node_add_attribute (helper->current, "@comment-tmp", join);
785 }
786 }
787
788 static AsNode *
as_node_from_xml_internal(const gchar * data,gssize data_sz,AsNodeFromXmlFlags flags,GError ** error)789 as_node_from_xml_internal (const gchar *data, gssize data_sz,
790 AsNodeFromXmlFlags flags,
791 GError **error)
792 {
793 AsNodeToXmlHelper helper;
794 AsNode *root = NULL;
795 gboolean ret;
796 g_autoptr(GError) error_local = NULL;
797 g_autoptr(GMarkupParseContext) ctx = NULL;
798 const GMarkupParser parser = {
799 as_node_start_element_cb,
800 as_node_end_element_cb,
801 as_node_text_cb,
802 as_node_passthrough_cb,
803 NULL };
804
805 g_return_val_if_fail (data != NULL, NULL);
806
807 root = as_node_new ();
808 helper.flags = flags;
809 helper.current = root;
810 helper.locales = g_get_language_names ();
811 ctx = g_markup_parse_context_new (&parser,
812 G_MARKUP_PREFIX_ERROR_POSITION,
813 &helper,
814 NULL);
815 ret = g_markup_parse_context_parse (ctx, data, data_sz, &error_local);
816 if (!ret) {
817 g_set_error_literal (error,
818 AS_NODE_ERROR,
819 AS_NODE_ERROR_FAILED,
820 error_local->message);
821 as_node_unref (root);
822 return NULL;
823 }
824
825 /* more opening than closing */
826 if (root != helper.current) {
827 g_set_error_literal (error,
828 AS_NODE_ERROR,
829 AS_NODE_ERROR_FAILED,
830 "Mismatched XML");
831 as_node_unref (root);
832 return NULL;
833 }
834 return root;
835 }
836
837 /**
838 * as_node_from_bytes: (skip)
839 * @bytes: a #GBytes
840 * @flags: #AsNodeFromXmlFlags, e.g. %AS_NODE_FROM_XML_FLAG_NONE
841 * @error: A #GError or %NULL
842 *
843 * Parses XML data into a DOM tree.
844 *
845 * Returns: (transfer none): A populated #AsNode tree
846 *
847 * Since: 0.7.6
848 **/
849 AsNode *
as_node_from_bytes(GBytes * bytes,AsNodeFromXmlFlags flags,GError ** error)850 as_node_from_bytes (GBytes *bytes, AsNodeFromXmlFlags flags, GError **error)
851 {
852 gsize sz = 0;
853 const gchar *buf;
854 g_return_val_if_fail (bytes != NULL, NULL);
855 buf = g_bytes_get_data (bytes, &sz);
856 return as_node_from_xml_internal (buf, (gssize) sz, flags, error);
857 }
858
859 /**
860 * as_node_from_xml: (skip)
861 * @data: XML data
862 * @flags: #AsNodeFromXmlFlags, e.g. %AS_NODE_FROM_XML_FLAG_NONE
863 * @error: A #GError or %NULL
864 *
865 * Parses XML data into a DOM tree.
866 *
867 * Returns: (transfer none): A populated #AsNode tree
868 *
869 * Since: 0.1.0
870 **/
871 AsNode *
as_node_from_xml(const gchar * data,AsNodeFromXmlFlags flags,GError ** error)872 as_node_from_xml (const gchar *data, AsNodeFromXmlFlags flags, GError **error)
873 {
874 return as_node_from_xml_internal (data, -1, flags, error);
875 }
876
877 /**
878 * as_node_to_file: (skip)
879 * @root: A populated #AsNode tree
880 * @file: a #GFile
881 * @flags: #AsNodeToXmlFlags, e.g. %AS_NODE_TO_XML_FLAG_NONE
882 * @cancellable: A #GCancellable, or %NULL
883 * @error: A #GError or %NULL
884 *
885 * Exports a DOM tree to an XML file.
886 *
887 * Returns: %TRUE for success
888 *
889 * Since: 0.2.0
890 **/
891 gboolean
as_node_to_file(const AsNode * root,GFile * file,AsNodeToXmlFlags flags,GCancellable * cancellable,GError ** error)892 as_node_to_file (const AsNode *root,
893 GFile *file,
894 AsNodeToXmlFlags flags,
895 GCancellable *cancellable,
896 GError **error)
897 {
898 g_autoptr(GString) xml = NULL;
899 xml = as_node_to_xml (root, flags);
900 return g_file_replace_contents (file,
901 xml->str,
902 xml->len,
903 NULL,
904 FALSE,
905 G_FILE_CREATE_NONE,
906 NULL,
907 cancellable,
908 error);
909 }
910
911 /**
912 * as_node_from_file: (skip)
913 * @file: file
914 * @flags: #AsNodeFromXmlFlags, e.g. %AS_NODE_FROM_XML_FLAG_NONE
915 * @cancellable: A #GCancellable, or %NULL
916 * @error: A #GError or %NULL
917 *
918 * Parses an XML file into a DOM tree.
919 *
920 * Returns: (transfer none): A populated #AsNode tree
921 *
922 * Since: 0.1.0
923 **/
924 AsNode *
as_node_from_file(GFile * file,AsNodeFromXmlFlags flags,GCancellable * cancellable,GError ** error)925 as_node_from_file (GFile *file,
926 AsNodeFromXmlFlags flags,
927 GCancellable *cancellable,
928 GError **error)
929 {
930 AsNodeToXmlHelper helper;
931 GError *error_local = NULL;
932 AsNode *root = NULL;
933 const gchar *content_type = NULL;
934 gboolean ret = TRUE;
935 gsize chunk_size = 32 * 1024;
936 gssize len;
937 g_autofree gchar *data = NULL;
938 g_autofree gchar *mime_type = NULL;
939 g_autoptr(GMarkupParseContext) ctx = NULL;
940 g_autoptr(GConverter) conv = NULL;
941 g_autoptr(GFileInfo) info = NULL;
942 g_autoptr(GInputStream) file_stream = NULL;
943 g_autoptr(GInputStream) stream_data = NULL;
944 const GMarkupParser parser = {
945 as_node_start_element_cb,
946 as_node_end_element_cb,
947 as_node_text_cb,
948 as_node_passthrough_cb,
949 NULL };
950
951 /* what kind of file is this */
952 info = g_file_query_info (file,
953 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
954 G_FILE_QUERY_INFO_NONE,
955 cancellable,
956 error);
957 if (info == NULL)
958 return NULL;
959
960 /* decompress if required */
961 file_stream = G_INPUT_STREAM (g_file_read (file, cancellable, error));
962 if (file_stream == NULL)
963 return NULL;
964 content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
965 mime_type = g_content_type_get_mime_type (content_type);
966 if (mime_type != NULL) {
967 if (g_strcmp0 (mime_type, "application/gzip") == 0 ||
968 g_strcmp0 (mime_type, "application/x-gzip") == 0) {
969 conv = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP));
970 stream_data = g_converter_input_stream_new (file_stream, conv);
971 } else if (g_strcmp0 (mime_type, "application/xml") == 0 ||
972 g_strcmp0 (mime_type, "text/xml") == 0) {
973 stream_data = g_object_ref (file_stream);
974 }
975 }
976 if (stream_data == NULL) {
977 g_set_error (error,
978 AS_NODE_ERROR,
979 AS_NODE_ERROR_FAILED,
980 "cannot process file of type %s", content_type);
981 return NULL;
982 }
983
984 /* parse */
985 root = as_node_new ();
986 helper.flags = flags;
987 helper.current = root;
988 helper.locales = g_get_language_names ();
989 ctx = g_markup_parse_context_new (&parser,
990 G_MARKUP_PREFIX_ERROR_POSITION,
991 &helper,
992 NULL);
993
994 data = g_malloc (chunk_size);
995 while ((len = g_input_stream_read (stream_data,
996 data,
997 chunk_size,
998 cancellable,
999 error)) > 0) {
1000 ret = g_markup_parse_context_parse (ctx,
1001 data,
1002 len,
1003 &error_local);
1004 if (!ret) {
1005 g_set_error_literal (error,
1006 AS_NODE_ERROR,
1007 AS_NODE_ERROR_FAILED,
1008 error_local->message);
1009 g_error_free (error_local);
1010 as_node_unref (root);
1011 return NULL;
1012 }
1013 }
1014 if (len < 0) {
1015 as_node_unref (root);
1016 return NULL;
1017 }
1018
1019 /* more opening than closing */
1020 if (root != helper.current) {
1021 g_set_error_literal (error,
1022 AS_NODE_ERROR,
1023 AS_NODE_ERROR_FAILED,
1024 "Mismatched XML");
1025 as_node_unref (root);
1026 return NULL;
1027 }
1028 return root;
1029 }
1030
1031 static AsNode *
as_node_get_child_node(const AsNode * root,const gchar * name,const gchar * attr_key,const gchar * attr_value)1032 as_node_get_child_node (const AsNode *root, const gchar *name,
1033 const gchar *attr_key, const gchar *attr_value)
1034 {
1035 AsNodeData *data;
1036 AsNode *node;
1037
1038 /* invalid */
1039 if (root == NULL)
1040 return NULL;
1041 if (name == NULL || name[0] == '\0')
1042 return NULL;
1043
1044 /* find a node called name */
1045 for (node = root->children; node != NULL; node = node->next) {
1046 data = node->data;
1047 if (data == NULL)
1048 return NULL;
1049 if (g_strcmp0 (as_tag_data_get_name (data), name) == 0) {
1050 if (attr_key != NULL) {
1051 if (g_strcmp0 (as_node_get_attribute (node, attr_key),
1052 attr_value) != 0) {
1053 continue;
1054 }
1055 }
1056 return node;
1057 }
1058 }
1059 return NULL;
1060 }
1061
1062 /**
1063 * as_node_get_name:
1064 * @node: a #AsNode
1065 *
1066 * Gets the node name, e.g. "body"
1067 *
1068 * Return value: string value
1069 *
1070 * Since: 0.1.0
1071 **/
1072 const gchar *
as_node_get_name(const AsNode * node)1073 as_node_get_name (const AsNode *node)
1074 {
1075 g_return_val_if_fail (node != NULL, NULL);
1076 if (node->data == NULL)
1077 return NULL;
1078 return as_tag_data_get_name ((AsNodeData *) node->data);
1079 }
1080
1081 /**
1082 * as_node_set_name: (skip)
1083 * @node: a #AsNode
1084 * @name: the new name
1085 *
1086 * Sets the node name, e.g. "body"
1087 *
1088 * Since: 0.1.4
1089 **/
1090 void
as_node_set_name(AsNode * node,const gchar * name)1091 as_node_set_name (AsNode *node, const gchar *name)
1092 {
1093 AsNodeData *data;
1094 AsNode *root = g_node_get_root (node);
1095
1096 g_return_if_fail (node != NULL);
1097
1098 if (node->data == NULL)
1099 return;
1100 data = node->data;
1101 if (data == NULL)
1102 return;
1103
1104 /* overwrite */
1105 if (!data->is_tag_valid) {
1106 if (!data->is_name_const)
1107 as_ref_string_unref (data->name);
1108 data->name = NULL;
1109 }
1110 as_node_data_set_name (root, data, name, AS_NODE_INSERT_FLAG_NONE);
1111 }
1112
1113 /**
1114 * as_node_get_data_as_refstr: (skip)
1115 * @node: a #AsNode
1116 *
1117 * Gets the node data, e.g. "paragraph text"
1118 *
1119 * Return value: string value
1120 *
1121 * Since: 0.6.11
1122 **/
1123 AsRefString *
as_node_get_data_as_refstr(const AsNode * node)1124 as_node_get_data_as_refstr (const AsNode *node)
1125 {
1126 AsNodeData *data;
1127 g_return_val_if_fail (node != NULL, NULL);
1128 if (node->data == NULL)
1129 return NULL;
1130 data = (AsNodeData *) node->data;
1131 if (data->is_root_node)
1132 return NULL;
1133 if (data->cdata == NULL || data->cdata[0] == '\0')
1134 return NULL;
1135 as_node_cdata_to_raw (data);
1136 return data->cdata;
1137 }
1138
1139 /**
1140 * as_node_get_data:
1141 * @node: a #AsNode
1142 *
1143 * Gets the node data, e.g. "paragraph text"
1144 *
1145 * Return value: string value
1146 *
1147 * Since: 0.1.0
1148 **/
1149 const gchar *
as_node_get_data(const AsNode * node)1150 as_node_get_data (const AsNode *node)
1151 {
1152 return as_node_get_data_as_refstr (node);
1153 }
1154
1155 /**
1156 * as_node_get_comment:
1157 * @node: a #AsNode
1158 *
1159 * Gets the node data, e.g. "Copyright 2014 Richard Hughes"
1160 *
1161 * Return value: string value, or %NULL
1162 *
1163 * Since: 0.1.6
1164 **/
1165 const gchar *
as_node_get_comment(const AsNode * node)1166 as_node_get_comment (const AsNode *node)
1167 {
1168 return as_node_get_attribute (node, "@comment");
1169 }
1170
1171 /**
1172 * as_node_get_tag:
1173 * @node: a #AsNode
1174 *
1175 * Gets the node tag enum.
1176 *
1177 * Return value: #AsTag, e.g. %AS_TAG_PKGNAME
1178 *
1179 * Since: 0.1.2
1180 **/
1181 AsTag
as_node_get_tag(const AsNode * node)1182 as_node_get_tag (const AsNode *node)
1183 {
1184 AsNodeData *data;
1185 const gchar *tmp;
1186
1187 g_return_val_if_fail (node != NULL, AS_TAG_UNKNOWN);
1188
1189 data = (AsNodeData *) node->data;
1190 if (data == NULL)
1191 return AS_TAG_UNKNOWN;
1192
1193 /* try to match with a fallback */
1194 if (!data->is_tag_valid) {
1195 tmp = as_tag_data_get_name (data);
1196 return as_tag_from_string_full (tmp, AS_TAG_FLAG_USE_FALLBACKS);
1197 }
1198
1199 return data->tag;
1200 }
1201
1202 /**
1203 * as_node_set_data: (skip)
1204 * @node: a #AsNode
1205 * @cdata: new data
1206 * @insert_flags: any %AsNodeInsertFlags.
1207 *
1208 * Sets new data on a node.
1209 *
1210 * Since: 0.1.1
1211 **/
1212 void
as_node_set_data(AsNode * node,const gchar * cdata,AsNodeInsertFlags insert_flags)1213 as_node_set_data (AsNode *node,
1214 const gchar *cdata,
1215 AsNodeInsertFlags insert_flags)
1216 {
1217 AsNodeData *data;
1218
1219 g_return_if_fail (node != NULL);
1220
1221 if (node->data == NULL)
1222 return;
1223
1224 data = (AsNodeData *) node->data;
1225 if (data->is_root_node)
1226 return;
1227 as_ref_string_assign_safe (&data->cdata, cdata);
1228 data->is_cdata_escaped = insert_flags & AS_NODE_INSERT_FLAG_PRE_ESCAPED;
1229 }
1230
1231 /**
1232 * as_node_set_comment: (skip)
1233 * @node: a #AsNode
1234 * @comment: new comment
1235 *
1236 * Sets new comment for the node.
1237 *
1238 * Since: 0.1.6
1239 **/
1240 void
as_node_set_comment(AsNode * node,const gchar * comment)1241 as_node_set_comment (AsNode *node, const gchar *comment)
1242 {
1243 as_node_add_attribute (node, "@comment", comment);
1244 }
1245
1246 /**
1247 * as_node_get_attribute_as_int:
1248 * @node: a #AsNode
1249 * @key: the attribute key
1250 *
1251 * Gets a node attribute, e.g. 34
1252 *
1253 * Return value: integer value, or %G_MAXINT for error
1254 *
1255 * Since: 0.1.0
1256 **/
1257 gint
as_node_get_attribute_as_int(const AsNode * node,const gchar * key)1258 as_node_get_attribute_as_int (const AsNode *node, const gchar *key)
1259 {
1260 const gchar *tmp;
1261 gchar *endptr = NULL;
1262 gint64 value_tmp;
1263
1264 tmp = as_node_get_attribute (node, key);
1265 if (tmp == NULL)
1266 return G_MAXINT;
1267 value_tmp = g_ascii_strtoll (tmp, &endptr, 10);
1268 if (value_tmp == 0 && tmp == endptr)
1269 return G_MAXINT;
1270 if (value_tmp > G_MAXINT || value_tmp < G_MININT)
1271 return G_MAXINT;
1272 return (gint) value_tmp;
1273 }
1274
1275 /**
1276 * as_node_get_attribute_as_uint:
1277 * @node: a #AsNode
1278 * @key: the attribute key
1279 *
1280 * Gets a node attribute, e.g. 34
1281 *
1282 * Return value: integer value, or %G_MAXINT for error
1283 *
1284 * Since: 0.6.1
1285 **/
1286 guint
as_node_get_attribute_as_uint(const AsNode * node,const gchar * key)1287 as_node_get_attribute_as_uint (const AsNode *node, const gchar *key)
1288 {
1289 const gchar *tmp;
1290 gchar *endptr = NULL;
1291 guint64 value_tmp;
1292
1293 tmp = as_node_get_attribute (node, key);
1294 if (tmp == NULL)
1295 return G_MAXUINT;
1296 value_tmp = g_ascii_strtoull (tmp, &endptr, 10);
1297 if (value_tmp == 0 && tmp == endptr)
1298 return G_MAXUINT;
1299 if (value_tmp > G_MAXUINT)
1300 return G_MAXUINT;
1301 return (guint) value_tmp;
1302 }
1303
1304 /**
1305 * as_node_get_attribute_as_refstr: (skip)
1306 * @node: a #AsNode
1307 * @key: the attribute key
1308 *
1309 * Gets a node attribute, e.g. "false"
1310 *
1311 * Return value: string value
1312 *
1313 * Since: 0.6.11
1314 **/
1315 AsRefString *
as_node_get_attribute_as_refstr(const AsNode * node,const gchar * key)1316 as_node_get_attribute_as_refstr (const AsNode *node, const gchar *key)
1317 {
1318 AsNodeData *data;
1319
1320 g_return_val_if_fail (node != NULL, NULL);
1321 g_return_val_if_fail (key != NULL, NULL);
1322
1323 if (node->data == NULL)
1324 return NULL;
1325 data = (AsNodeData *) node->data;
1326 return as_node_attr_lookup (data, key);
1327 }
1328
1329 /**
1330 * as_node_get_attribute:
1331 * @node: a #AsNode
1332 * @key: the attribute key
1333 *
1334 * Gets a node attribute, e.g. "false"
1335 *
1336 * Return value: string value
1337 *
1338 * Since: 0.1.0
1339 **/
1340 const gchar *
as_node_get_attribute(const AsNode * node,const gchar * key)1341 as_node_get_attribute (const AsNode *node, const gchar *key)
1342 {
1343 return as_node_get_attribute_as_refstr (node, key);
1344 }
1345
1346 /**
1347 * as_node_remove_attribute: (skip)
1348 * @node: a #AsNode
1349 * @key: the attribute key
1350 *
1351 * Removes a node attribute, e.g. "type"
1352 *
1353 * Since: 0.2.0
1354 **/
1355 void
as_node_remove_attribute(AsNode * node,const gchar * key)1356 as_node_remove_attribute (AsNode *node, const gchar *key)
1357 {
1358 AsNodeAttr *attr;
1359 AsNodeData *data;
1360
1361 g_return_if_fail (node != NULL);
1362 g_return_if_fail (key != NULL);
1363
1364 if (node->data == NULL)
1365 return;
1366 data = (AsNodeData *) node->data;
1367 attr = as_node_attr_find (data, key);
1368 if (attr == NULL)
1369 return;
1370 data->attrs = g_list_remove_all (data->attrs, attr);
1371 as_node_attr_free (attr);
1372 }
1373
1374 /**
1375 * as_node_add_attribute: (skip)
1376 * @node: a #AsNode
1377 * @key: the attribute key
1378 * @value: new data
1379 *
1380 * Adds a new attribute to a node.
1381 *
1382 * Since: 0.1.1
1383 **/
1384 void
as_node_add_attribute(AsNode * node,const gchar * key,const gchar * value)1385 as_node_add_attribute (AsNode *node,
1386 const gchar *key,
1387 const gchar *value)
1388 {
1389 AsNodeData *data;
1390 AsNode *root = g_node_get_root (node);
1391
1392 g_return_if_fail (node != NULL);
1393 g_return_if_fail (key != NULL);
1394
1395 if (node->data == NULL)
1396 return;
1397 data = (AsNodeData *) node->data;
1398 as_node_attr_insert (root, data, key, value);
1399 }
1400
1401 /**
1402 * as_node_add_attribute_as_int: (skip)
1403 * @node: a #AsNode
1404 * @key: the attribute key
1405 * @value: new data
1406 *
1407 * Adds a new attribute to a node.
1408 *
1409 * Since: 0.3.1
1410 **/
1411 void
as_node_add_attribute_as_int(AsNode * node,const gchar * key,gint value)1412 as_node_add_attribute_as_int (AsNode *node, const gchar *key, gint value)
1413 {
1414 g_autofree gchar *tmp = g_strdup_printf ("%i", value);
1415 as_node_add_attribute (node, key, tmp);
1416 }
1417
1418 /**
1419 * as_node_add_attribute_as_uint: (skip)
1420 * @node: a #AsNode
1421 * @key: the attribute key
1422 * @value: new data
1423 *
1424 * Adds a new attribute to a node.
1425 *
1426 * Since: 0.6.1
1427 **/
1428 void
as_node_add_attribute_as_uint(AsNode * node,const gchar * key,guint value)1429 as_node_add_attribute_as_uint (AsNode *node, const gchar *key, guint value)
1430 {
1431 g_autofree gchar *tmp = g_strdup_printf ("%u", value);
1432 as_node_add_attribute (node, key, tmp);
1433 }
1434
1435 /**
1436 * as_node_find: (skip)
1437 * @root: a root node, or %NULL
1438 * @path: a path in the DOM, e.g. "html/body"
1439 *
1440 * Gets a node from the DOM tree.
1441 *
1442 * Return value: A #AsNode, or %NULL if not found
1443 *
1444 * Since: 0.1.0
1445 **/
1446 AsNode *
as_node_find(AsNode * root,const gchar * path)1447 as_node_find (AsNode *root, const gchar *path)
1448 {
1449 AsNode *node = root;
1450 guint i;
1451 g_auto(GStrv) split = NULL;
1452
1453 g_return_val_if_fail (path != NULL, NULL);
1454
1455 split = g_strsplit (path, "/", -1);
1456 for (i = 0; split[i] != NULL; i++) {
1457 node = as_node_get_child_node (node, split[i], NULL, NULL);
1458 if (node == NULL)
1459 return NULL;
1460 }
1461 return node;
1462 }
1463
1464 /**
1465 * as_node_find_with_attribute: (skip)
1466 * @root: a root node, or %NULL
1467 * @path: a path in the DOM, e.g. "html/body"
1468 * @attr_key: the attribute key
1469 * @attr_value: the attribute value
1470 *
1471 * Gets a node from the DOM tree with a specified attribute.
1472 *
1473 * Return value: A #AsNode, or %NULL if not found
1474 *
1475 * Since: 0.1.0
1476 **/
1477 AsNode *
as_node_find_with_attribute(AsNode * root,const gchar * path,const gchar * attr_key,const gchar * attr_value)1478 as_node_find_with_attribute (AsNode *root, const gchar *path,
1479 const gchar *attr_key, const gchar *attr_value)
1480 {
1481 AsNode *node = root;
1482 guint i;
1483 g_auto(GStrv) split = NULL;
1484
1485 g_return_val_if_fail (path != NULL, NULL);
1486
1487 split = g_strsplit (path, "/", -1);
1488 for (i = 0; split[i] != NULL; i++) {
1489 /* only check the last element */
1490 if (split[i + 1] == NULL) {
1491 node = as_node_get_child_node (node, split[i],
1492 attr_key, attr_value);
1493 if (node == NULL)
1494 return NULL;
1495 } else {
1496 node = as_node_get_child_node (node, split[i], NULL, NULL);
1497 if (node == NULL)
1498 return NULL;
1499 }
1500 }
1501 return node;
1502 }
1503
1504 static AsRefString *
as_node_insert_line_breaks(const gchar * text,guint break_len)1505 as_node_insert_line_breaks (const gchar *text, guint break_len)
1506 {
1507 guint i;
1508 gssize new_len;
1509 g_autoptr(GString) str = NULL;
1510
1511 /* allocate long enough for the string, plus the extra newlines */
1512 new_len = (gssize) (strlen (text) * (break_len + 1) / break_len);
1513 str = g_string_new_len (NULL, new_len + 2);
1514 g_string_append (str, "\n");
1515 g_string_append (str, text);
1516
1517 /* insert a newline every break length */
1518 for (i = break_len + 1; i < str->len; i += break_len + 1)
1519 g_string_insert (str, (gssize) i, "\n");
1520 g_string_append (str, "\n");
1521 return as_ref_string_new_with_length (str->str, str->len);
1522 }
1523
1524 /**
1525 * as_node_insert: (skip)
1526 * @parent: a parent #AsNode.
1527 * @name: the tag name, e.g. "id".
1528 * @cdata: the tag data, or %NULL, e.g. "org.gnome.Software.desktop".
1529 * @insert_flags: any %AsNodeInsertFlags.
1530 * @...: any attributes to add to the node, terminated by %NULL
1531 *
1532 * Inserts a node into the DOM.
1533 *
1534 * Returns: (transfer none): A populated #AsNode
1535 *
1536 * Since: 0.1.0
1537 **/
1538 AsNode *
as_node_insert(AsNode * parent,const gchar * name,const gchar * cdata,AsNodeInsertFlags insert_flags,...)1539 as_node_insert (AsNode *parent,
1540 const gchar *name,
1541 const gchar *cdata,
1542 AsNodeInsertFlags insert_flags,
1543 ...)
1544 {
1545 const gchar *key;
1546 const gchar *value;
1547 AsNodeData *data;
1548 AsNode *root = g_node_get_root (parent);
1549 guint i;
1550 va_list args;
1551
1552 g_return_val_if_fail (name != NULL, NULL);
1553
1554 data = g_slice_new0 (AsNodeData);
1555 as_node_data_set_name (root, data, name, insert_flags);
1556 if (cdata != NULL) {
1557 if (insert_flags & AS_NODE_INSERT_FLAG_BASE64_ENCODED)
1558 data->cdata = as_node_insert_line_breaks (cdata, 76);
1559 else
1560 data->cdata = as_ref_string_new (cdata);
1561 }
1562 data->is_cdata_escaped = insert_flags & AS_NODE_INSERT_FLAG_PRE_ESCAPED;
1563
1564 /* process the attrs valist */
1565 va_start (args, insert_flags);
1566 for (i = 0;; i++) {
1567 key = va_arg (args, const gchar *);
1568 if (key == NULL)
1569 break;
1570 value = va_arg (args, const gchar *);
1571 if (value == NULL)
1572 break;
1573 as_node_attr_insert (root, data, key, value);
1574 }
1575 va_end (args);
1576
1577 return g_node_insert_data (parent, -1, data);
1578 }
1579
1580 static gint
as_node_list_sort_cb(gconstpointer a,gconstpointer b)1581 as_node_list_sort_cb (gconstpointer a, gconstpointer b)
1582 {
1583 return g_strcmp0 ((const gchar *) a, (const gchar *) b);
1584 }
1585
1586 /**
1587 * as_node_insert_localized:
1588 * @parent: a parent #AsNode.
1589 * @name: the tag name, e.g. "id".
1590 * @localized: the hash table of data, with the locale as the key.
1591 * @insert_flags: any %AsNodeInsertFlags.
1592 *
1593 * Inserts a localized key into the DOM.
1594 *
1595 * Since: 0.1.0
1596 **/
1597 void
as_node_insert_localized(AsNode * parent,const gchar * name,GHashTable * localized,AsNodeInsertFlags insert_flags)1598 as_node_insert_localized (AsNode *parent,
1599 const gchar *name,
1600 GHashTable *localized,
1601 AsNodeInsertFlags insert_flags)
1602 {
1603 AsNodeData *data;
1604 AsNode *root = g_node_get_root (parent);
1605 GList *l;
1606 const gchar *key;
1607 const gchar *value;
1608 const gchar *value_c;
1609 g_autoptr(GList) list = NULL;
1610
1611 g_return_if_fail (name != NULL);
1612
1613 /* add the untranslated value first */
1614 value_c = g_hash_table_lookup (localized, "C");
1615 if (value_c == NULL)
1616 return;
1617 data = g_slice_new0 (AsNodeData);
1618 as_node_data_set_name (root, data, name, insert_flags);
1619 if (insert_flags & AS_NODE_INSERT_FLAG_NO_MARKUP) {
1620 g_autofree gchar *tmp = as_markup_convert_simple (value_c, NULL);
1621 data->cdata = as_ref_string_new (tmp);
1622 data->is_cdata_escaped = FALSE;
1623 } else {
1624 data->cdata = as_ref_string_new (value_c);
1625 data->is_cdata_escaped = insert_flags & AS_NODE_INSERT_FLAG_PRE_ESCAPED;
1626 }
1627 g_node_insert_data (parent, -1, data);
1628
1629 /* add the other localized values */
1630 list = g_hash_table_get_keys (localized);
1631 list = g_list_sort (list, as_node_list_sort_cb);
1632 for (l = list; l != NULL; l = l->next) {
1633 key = l->data;
1634 if (g_strcmp0 (key, "C") == 0)
1635 continue;
1636 if (g_strcmp0 (key, "x-test") == 0)
1637 continue;
1638 value = g_hash_table_lookup (localized, key);
1639 if ((insert_flags & AS_NODE_INSERT_FLAG_DEDUPE_LANG) > 0 &&
1640 g_strcmp0 (value_c, value) == 0)
1641 continue;
1642 data = g_slice_new0 (AsNodeData);
1643 as_node_attr_insert (root, data, "xml:lang", key);
1644 as_node_data_set_name (root, data, name, insert_flags);
1645 if (insert_flags & AS_NODE_INSERT_FLAG_NO_MARKUP) {
1646 g_autofree gchar *tmp = as_markup_convert_simple (value, NULL);
1647 data->cdata = as_ref_string_new (tmp);
1648 data->is_cdata_escaped = FALSE;
1649 } else {
1650 data->cdata = as_ref_string_new (value);
1651 data->is_cdata_escaped = insert_flags & AS_NODE_INSERT_FLAG_PRE_ESCAPED;
1652 }
1653 g_node_insert_data (parent, -1, data);
1654 }
1655 }
1656
1657 /**
1658 * as_node_insert_hash:
1659 * @parent: a parent #AsNode.
1660 * @name: the tag name, e.g. "id".
1661 * @attr_key: the key to use as the attribute in the XML, e.g. "key".
1662 * @hash: the hash table with the key as the key to use in the XML.
1663 * @insert_flags: any %AsNodeInsertFlags.
1664 *
1665 * Inserts a hash table of data into the DOM.
1666 *
1667 * Since: 0.1.0
1668 **/
1669 void
as_node_insert_hash(AsNode * parent,const gchar * name,const gchar * attr_key,GHashTable * hash,AsNodeInsertFlags insert_flags)1670 as_node_insert_hash (AsNode *parent,
1671 const gchar *name,
1672 const gchar *attr_key,
1673 GHashTable *hash,
1674 AsNodeInsertFlags insert_flags)
1675 {
1676 AsNodeData *data;
1677 AsNode *root = g_node_get_root (parent);
1678 GList *l;
1679 GList *list;
1680 const gchar *key;
1681 const gchar *value;
1682 gboolean swapped = (insert_flags & AS_NODE_INSERT_FLAG_SWAPPED) > 0;
1683
1684 g_return_if_fail (name != NULL);
1685
1686 list = g_hash_table_get_keys (hash);
1687 list = g_list_sort (list, as_node_list_sort_cb);
1688 for (l = list; l != NULL; l = l->next) {
1689 key = l->data;
1690 value = g_hash_table_lookup (hash, key);
1691 data = g_slice_new0 (AsNodeData);
1692 as_node_data_set_name (root, data, name, insert_flags);
1693 data->cdata = as_ref_string_new (!swapped ? value : key);
1694 data->is_cdata_escaped = insert_flags & AS_NODE_INSERT_FLAG_PRE_ESCAPED;
1695 if (!swapped) {
1696 if (key != NULL && key[0] != '\0')
1697 as_node_attr_insert (root, data, attr_key, key);
1698 } else {
1699 if (value != NULL && value[0] != '\0')
1700 as_node_attr_insert (root, data, attr_key, value);
1701 }
1702 g_node_insert_data (parent, -1, data);
1703 }
1704 g_list_free (list);
1705 }
1706
1707 /**
1708 * as_node_get_localized:
1709 * @node: a #AsNode
1710 * @key: the key to use, e.g. "copyright"
1711 *
1712 * Extracts localized values from the DOM tree
1713 *
1714 * Return value: (transfer full): A hash table with the locale (e.g. en_GB) as the key
1715 *
1716 * Since: 0.1.0
1717 **/
1718 GHashTable *
as_node_get_localized(const AsNode * node,const gchar * key)1719 as_node_get_localized (const AsNode *node, const gchar *key)
1720 {
1721 AsNodeData *data;
1722 AsRefString *data_unlocalized;
1723 AsRefString *xml_lang;
1724 GHashTable *hash = NULL;
1725 AsNode *tmp;
1726 g_autoptr(AsRefString) xml_lang_c = as_ref_string_new_static ("C");
1727
1728 /* does it exist? */
1729 tmp = as_node_get_child_node (node, key, NULL, NULL);
1730 if (tmp == NULL)
1731 return NULL;
1732 data_unlocalized = as_node_get_data_as_refstr (tmp);
1733
1734 /* find a node called name */
1735 hash = g_hash_table_new_full (g_str_hash, g_str_equal,
1736 (GDestroyNotify) as_ref_string_unref, NULL);
1737 for (tmp = node->children; tmp != NULL; tmp = tmp->next) {
1738 data = tmp->data;
1739 if (data == NULL)
1740 continue;
1741 if (data->cdata == NULL)
1742 continue;
1743 if (g_strcmp0 (as_tag_data_get_name (data), key) != 0)
1744 continue;
1745 xml_lang = as_node_attr_lookup (data, "xml:lang");
1746 if (g_strcmp0 (xml_lang, "x-test") == 0)
1747 continue;
1748
1749 g_hash_table_insert (hash,
1750 as_ref_string_ref (xml_lang != NULL ? xml_lang : xml_lang_c),
1751 (gpointer) data->cdata);
1752 }
1753 return hash;
1754 }
1755
1756 /**
1757 * as_node_get_localized_best:
1758 * @node: a #AsNode.
1759 * @key: the tag name.
1760 *
1761 * Gets the 'best' locale version of a specific data value.
1762 *
1763 * Returns: the string value, or %NULL if there was no data
1764 *
1765 * Since: 0.1.0
1766 **/
1767 const gchar *
as_node_get_localized_best(const AsNode * node,const gchar * key)1768 as_node_get_localized_best (const AsNode *node, const gchar *key)
1769 {
1770 g_autoptr(GHashTable) hash = NULL;
1771 hash = as_node_get_localized (node, key);
1772 if (hash == NULL)
1773 return NULL;
1774 return as_hash_lookup_by_locale (hash, NULL);
1775 }
1776
1777 static void
as_node_string_free(GString * string)1778 as_node_string_free (GString *string)
1779 {
1780 g_string_free (string, TRUE);
1781 }
1782
1783 static void
as_node_denorm_add_to_langs(GHashTable * hash,AsTag tag,gboolean is_start)1784 as_node_denorm_add_to_langs (GHashTable *hash, AsTag tag, gboolean is_start)
1785 {
1786 GList *l;
1787 GString *str;
1788 const gchar *xml_lang;
1789 g_autoptr(GList) keys = NULL;
1790
1791 keys = g_hash_table_get_keys (hash);
1792 for (l = keys; l != NULL; l = l->next) {
1793 xml_lang = l->data;
1794 str = g_hash_table_lookup (hash, xml_lang);
1795 if (is_start)
1796 g_string_append_printf (str, "<%s>", as_tag_to_string (tag));
1797 else
1798 g_string_append_printf (str, "</%s>", as_tag_to_string (tag));
1799 }
1800 }
1801
1802 static GString *
as_node_denorm_get_str_for_lang(GHashTable * hash,AsNodeData * data,gboolean allow_new_locales)1803 as_node_denorm_get_str_for_lang (GHashTable *hash,
1804 AsNodeData *data,
1805 gboolean allow_new_locales)
1806 {
1807 const gchar *xml_lang = NULL;
1808 GString *str;
1809
1810 /* get locale */
1811 xml_lang = as_node_attr_lookup (data, "xml:lang");
1812 if (xml_lang == NULL)
1813 xml_lang = "C";
1814 str = g_hash_table_lookup (hash, xml_lang);
1815 if (str == NULL && allow_new_locales) {
1816 str = g_string_new ("");
1817 g_hash_table_insert (hash, g_strdup (xml_lang), str);
1818 }
1819 return str;
1820 }
1821
1822 /**
1823 * as_node_get_localized_unwrap_type_li:
1824 *
1825 * Denormalize AppData data where each <li> element is translated:
1826 *
1827 * |[
1828 * <description>
1829 * <p>Hi</p>
1830 * <p xml:lang="pl">Czesc</p>
1831 * <ul>
1832 * <li>First</li>
1833 * <li xml:lang="pl">Pierwszy</li>
1834 * </ul>
1835 * </description>
1836 * ]|
1837 **/
1838 static gboolean
as_node_get_localized_unwrap_type_li(const AsNode * node,GHashTable * hash,GError ** error)1839 as_node_get_localized_unwrap_type_li (const AsNode *node,
1840 GHashTable *hash,
1841 GError **error)
1842 {
1843 AsNode *tmp;
1844 AsNode *tmp_c;
1845 AsNodeData *data;
1846 AsNodeData *data_c;
1847 GString *str;
1848
1849 for (tmp = node->children; tmp != NULL; tmp = tmp->next) {
1850 data = tmp->data;
1851
1852 /* append to existing string, adding the locale if it's not
1853 * already present */
1854 if (data->tag == AS_TAG_P) {
1855 str = as_node_denorm_get_str_for_lang (hash, data, TRUE);
1856 as_node_cdata_to_escaped (data);
1857 g_string_append_printf (str, "<p>%s</p>",
1858 data->cdata);
1859
1860 /* loop on the children */
1861 } else if (data->tag == AS_TAG_UL || data->tag == AS_TAG_OL) {
1862 as_node_denorm_add_to_langs (hash, data->tag, TRUE);
1863 for (tmp_c = tmp->children; tmp_c != NULL; tmp_c = tmp_c->next) {
1864 data_c = tmp_c->data;
1865
1866 /* handle new locales that have list
1867 * translations but no paragraph translations */
1868 str = as_node_denorm_get_str_for_lang (hash,
1869 data_c,
1870 FALSE);
1871 if (str == NULL) {
1872 str = as_node_denorm_get_str_for_lang (hash,
1873 data_c,
1874 TRUE);
1875 g_string_append_printf (str, "<%s>",
1876 as_tag_data_get_name (data));
1877 }
1878 if (data_c->tag == AS_TAG_LI) {
1879 as_node_cdata_to_escaped (data_c);
1880 g_string_append_printf (str,
1881 "<li>%s</li>",
1882 data_c->cdata);
1883 } else {
1884 /* only <li> is valid in lists */
1885 g_set_error (error,
1886 AS_NODE_ERROR,
1887 AS_NODE_ERROR_INVALID_MARKUP,
1888 "Tag %s in %s invalid",
1889 as_tag_data_get_name (data_c),
1890 as_tag_data_get_name (data));
1891 return FALSE;
1892 }
1893 }
1894 as_node_denorm_add_to_langs (hash, data->tag, FALSE);
1895 } else {
1896 /* only <p>, <ul> and <ol> is valid here */
1897 g_set_error (error,
1898 AS_NODE_ERROR,
1899 AS_NODE_ERROR_INVALID_MARKUP,
1900 "Unknown tag '%s'",
1901 as_tag_data_get_name (data));
1902 return FALSE;
1903 }
1904 }
1905 return TRUE;
1906 }
1907
1908 /**
1909 * as_node_get_localized_unwrap_type_ul:
1910 *
1911 * Denormalize AppData data where the parent <ul> is translated:
1912 *
1913 * |[
1914 * <description>
1915 * <p>Hi</p>
1916 * <p xml:lang="pl">Czesc</p>
1917 * <ul xml:lang="pl">
1918 * <li>First</li>
1919 * </ul>
1920 * <ul xml:lang="pl">
1921 * <li>Pierwszy</li>
1922 * </ul>
1923 * </description>
1924 * ]|
1925 **/
1926 static gboolean
as_node_get_localized_unwrap_type_ul(const AsNode * node,GHashTable * hash,GError ** error)1927 as_node_get_localized_unwrap_type_ul (const AsNode *node,
1928 GHashTable *hash,
1929 GError **error)
1930 {
1931 AsNode *tmp;
1932 AsNode *tmp_c;
1933 AsNodeData *data;
1934 AsNodeData *data_c;
1935 GString *str;
1936
1937 for (tmp = node->children; tmp != NULL; tmp = tmp->next) {
1938 data = tmp->data;
1939
1940 /* append to existing string, adding the locale if it's not
1941 * already present */
1942 if (data->tag == AS_TAG_P) {
1943 str = as_node_denorm_get_str_for_lang (hash, data, TRUE);
1944 as_node_cdata_to_escaped (data);
1945 g_string_append_printf (str, "<p>%s</p>",
1946 data->cdata);
1947
1948 /* loop on the children */
1949 } else if (data->tag == AS_TAG_UL || data->tag == AS_TAG_OL) {
1950 str = as_node_denorm_get_str_for_lang (hash, data, TRUE);
1951 g_string_append_printf (str, "<%s>", as_tag_data_get_name (data));
1952 for (tmp_c = tmp->children; tmp_c != NULL; tmp_c = tmp_c->next) {
1953 data_c = tmp_c->data;
1954 if (data_c->tag == AS_TAG_LI) {
1955 as_node_cdata_to_escaped (data_c);
1956 g_string_append_printf (str,
1957 "<li>%s</li>",
1958 data_c->cdata);
1959 } else {
1960 /* only <li> is valid in lists */
1961 g_set_error (error,
1962 AS_NODE_ERROR,
1963 AS_NODE_ERROR_INVALID_MARKUP,
1964 "Tag %s in %s invalid",
1965 data_c->name,
1966 as_tag_data_get_name (data));
1967 return FALSE;
1968 }
1969 }
1970 g_string_append_printf (str, "</%s>", as_tag_data_get_name (data));
1971 } else {
1972 /* only <p>, <ul> and <ol> is valid here */
1973 g_set_error (error,
1974 AS_NODE_ERROR,
1975 AS_NODE_ERROR_INVALID_MARKUP,
1976 "Unknown tag '%s'",
1977 as_tag_data_get_name (data));
1978 return FALSE;
1979 }
1980 }
1981 return TRUE;
1982 }
1983
1984 /**
1985 * as_node_fix_locale_full: (skip)
1986 * @node: A #AsNode
1987 * @locale: The locale
1988 *
1989 * Fixes and filters incorrect locale strings using the root node to intern the
1990 * locale.
1991 *
1992 * Returns: (transfer full): a newly allocated string
1993 *
1994 * Since: 0.7.9
1995 **/
1996 AsRefString *
as_node_fix_locale_full(const GNode * node,const gchar * locale)1997 as_node_fix_locale_full (const GNode *node, const gchar *locale)
1998 {
1999 GNode *root = g_node_get_root ((GNode *) node);
2000 AsNodeRoot *root_data = ((AsNodeData *)root->data)->root;
2001
2002 if (locale == NULL || g_strcmp0 (locale, "C") == 0)
2003 return as_ref_string_new_static ("C");
2004 if (g_strcmp0 (locale, "xx") == 0)
2005 return NULL;
2006 if (g_strcmp0 (locale, "x-test") == 0)
2007 return NULL;
2008 return as_ref_string_ref (as_node_intern (root_data->intern_lang, locale));
2009 }
2010
2011 /**
2012 * as_node_fix_locale: (skip)
2013 * @locale: The locale
2014 *
2015 * Fixes and filters incorrect locale strings.
2016 *
2017 * Returns: a newly allocated string
2018 *
2019 * Since: 0.5.2
2020 **/
2021 AsRefString *
as_node_fix_locale(const gchar * locale)2022 as_node_fix_locale (const gchar *locale)
2023 {
2024 if (locale == NULL || g_strcmp0 (locale, "C") == 0)
2025 return as_ref_string_new_static ("C");
2026 if (g_strcmp0 (locale, "xx") == 0)
2027 return NULL;
2028 if (g_strcmp0 (locale, "x-test") == 0)
2029 return NULL;
2030 return as_ref_string_new (locale);
2031 }
2032
2033 /**
2034 * as_node_get_localized_unwrap:
2035 * @node: a #AsNode.
2036 * @error: A #GError or %NULL.
2037 *
2038 * Denormalize AppData data like this:
2039 *
2040 * |[
2041 * <description>
2042 * <p>Hi</p>
2043 * <p xml:lang="pl">Czesc</p>
2044 * <ul>
2045 * <li>First</li>
2046 * <li xml:lang="pl">Pierwszy</li>
2047 * </ul>
2048 * </description>
2049 * ]|
2050 *
2051 * into a hash that contains:
2052 *
2053 * |[
2054 * "C" -> "<p>Hi</p><ul><li>First</li></ul>"
2055 * "pl" -> "<p>Czesc</p><ul><li>Pierwszy</li></ul>"
2056 * ]|
2057 *
2058 * Returns: (transfer full): a hash table of data
2059 *
2060 * Since: 0.1.0
2061 **/
2062 GHashTable *
as_node_get_localized_unwrap(const AsNode * node,GError ** error)2063 as_node_get_localized_unwrap (const AsNode *node, GError **error)
2064 {
2065 AsNodeData *data;
2066 AsRefString *xml_lang;
2067 GList *l;
2068 AsNode *tmp;
2069 GString *str;
2070 gboolean is_li_translated = TRUE;
2071 g_autoptr(GHashTable) hash = NULL;
2072 g_autoptr(GHashTable) results = NULL;
2073 g_autoptr(GList) keys = NULL;
2074
2075 g_return_val_if_fail (node != NULL, NULL);
2076
2077 /* use AsRefString to be able to ref when subsuming */
2078 results = g_hash_table_new_full (g_str_hash, g_str_equal,
2079 (GDestroyNotify) as_ref_string_unref,
2080 (GDestroyNotify) as_ref_string_unref);
2081
2082 /* work out what kind of normalization this is */
2083 xml_lang = as_node_get_attribute_as_refstr (node, "xml:lang");
2084 if (xml_lang != NULL && node->children != NULL) {
2085 str = as_node_to_xml (node->children, AS_NODE_TO_XML_FLAG_NONE);
2086 g_hash_table_insert (results,
2087 as_ref_string_ref (xml_lang),
2088 as_ref_string_new (str->str));
2089 g_string_free (str, TRUE);
2090 return g_steal_pointer (&results);
2091 }
2092 for (tmp = node->children; tmp != NULL; tmp = tmp->next) {
2093 data = tmp->data;
2094 if (data->tag == AS_TAG_UL || data->tag == AS_TAG_OL) {
2095 if (as_node_attr_lookup (data, "xml:lang") != NULL) {
2096 is_li_translated = FALSE;
2097 break;
2098 }
2099 }
2100 }
2101
2102 /* unwrap it to a hash */
2103 hash = g_hash_table_new_full (g_str_hash, g_str_equal,
2104 g_free, (GDestroyNotify) as_node_string_free);
2105 if (is_li_translated) {
2106 if (!as_node_get_localized_unwrap_type_li (node, hash, error))
2107 return NULL;
2108 } else {
2109 if (!as_node_get_localized_unwrap_type_ul (node, hash, error))
2110 return NULL;
2111 }
2112
2113 /* copy into a hash table of the correct size */
2114 keys = g_hash_table_get_keys (hash);
2115 for (l = keys; l != NULL; l = l->next) {
2116 g_autoptr(AsRefString) locale_fixed = NULL;
2117 xml_lang = l->data;
2118 locale_fixed = as_node_fix_locale_full (node, xml_lang);
2119 if (locale_fixed == NULL)
2120 continue;
2121 str = g_hash_table_lookup (hash, xml_lang);
2122 g_hash_table_insert (results,
2123 as_ref_string_ref (locale_fixed),
2124 as_ref_string_new (str->str));
2125 }
2126 return g_steal_pointer (&results);
2127 }
2128
2129 /* helper struct */
2130 struct _AsNodeContext {
2131 AsFormatKind format_kind;
2132 AsFormatKind output;
2133 gdouble version;
2134 gboolean output_trusted;
2135 AsRefString *media_base_url;
2136 };
2137
2138 /**
2139 * as_node_context_new: (skip)
2140 *
2141 * Creates a new node context with default values.
2142 *
2143 * Returns: a #AsNodeContext
2144 *
2145 * Since: 0.3.6
2146 **/
2147 AsNodeContext *
as_node_context_new(void)2148 as_node_context_new (void)
2149 {
2150 AsNodeContext *ctx;
2151 ctx = g_new0 (AsNodeContext, 1);
2152 ctx->version = 0.f;
2153 ctx->format_kind = AS_FORMAT_KIND_APPSTREAM;
2154 ctx->output = AS_FORMAT_KIND_UNKNOWN;
2155 return ctx;
2156 }
2157
2158 /**
2159 * as_node_context_free: (skip)
2160 * @ctx: a #AsNodeContext.
2161 *
2162 * Frees the node context.
2163 *
2164 * Since: 0.6.4
2165 **/
2166 void
as_node_context_free(AsNodeContext * ctx)2167 as_node_context_free (AsNodeContext *ctx)
2168 {
2169 if (ctx == NULL)
2170 return;
2171 if (ctx->media_base_url != NULL)
2172 as_ref_string_unref (ctx->media_base_url);
2173 g_free (ctx);
2174 }
2175
2176 /**
2177 * as_node_context_get_version: (skip)
2178 * @ctx: a #AsNodeContext.
2179 *
2180 * Gets the AppStream API version used when parsing or inserting nodes.
2181 *
2182 * Returns: version number
2183 *
2184 * Since: 0.3.6
2185 **/
2186 gdouble
as_node_context_get_version(AsNodeContext * ctx)2187 as_node_context_get_version (AsNodeContext *ctx)
2188 {
2189 return ctx->version;
2190 }
2191
2192 /**
2193 * as_node_context_set_version: (skip)
2194 * @ctx: a #AsNodeContext.
2195 * @version: an API version number to target.
2196 *
2197 * Sets the AppStream API version used when parsing or inserting nodes.
2198 *
2199 * Since: 0.3.6
2200 **/
2201 void
as_node_context_set_version(AsNodeContext * ctx,gdouble version)2202 as_node_context_set_version (AsNodeContext *ctx, gdouble version)
2203 {
2204 ctx->version = version;
2205 }
2206
2207 /**
2208 * as_node_context_get_format_kind: (skip)
2209 * @ctx: a #AsNodeContext.
2210 *
2211 * Gets the AppStream API format kind used when parsing nodes.
2212 *
2213 * Returns: format kind, e.g. %AS_FORMAT_KIND_APPDATA
2214 *
2215 * Since: 0.6.9
2216 **/
2217 AsFormatKind
as_node_context_get_format_kind(AsNodeContext * ctx)2218 as_node_context_get_format_kind (AsNodeContext *ctx)
2219 {
2220 return ctx->format_kind;
2221 }
2222
2223 /**
2224 * as_node_context_get_source_kind: (skip)
2225 * @ctx: a #AsNodeContext.
2226 *
2227 * Gets the AppStream API format kind used when parsing nodes.
2228 *
2229 * Returns: format kind, e.g. %AS_FORMAT_KIND_APPDATA
2230 *
2231 * Since: 0.3.6
2232 * Deprecated: 0.9.6: Use as_node_context_get_format_kind() instead.
2233 **/
2234 AsFormatKind
as_node_context_get_source_kind(AsNodeContext * ctx)2235 as_node_context_get_source_kind (AsNodeContext *ctx)
2236 {
2237 return as_node_context_get_format_kind (ctx);
2238 }
2239
2240 /**
2241 * as_node_context_set_format_kind: (skip)
2242 * @ctx: a #AsNodeContext.
2243 * @format_kind: an API format kind, e.g. %AS_FORMAT_KIND_APPDATA
2244 *
2245 * Sets the AppStream API format kind used when exporting nodes.
2246 *
2247 * Since: 0.6.9
2248 **/
2249 void
as_node_context_set_format_kind(AsNodeContext * ctx,AsFormatKind format_kind)2250 as_node_context_set_format_kind (AsNodeContext *ctx, AsFormatKind format_kind)
2251 {
2252 ctx->format_kind = format_kind;
2253 }
2254
2255 /**
2256 * as_node_context_set_source_kind: (skip)
2257 * @ctx: a #AsNodeContext.
2258 * @source_kind: an API format kind, e.g. %AS_FORMAT_KIND_APPDATA
2259 *
2260 * Sets the AppStream API format kind used when exporting nodes.
2261 *
2262 * Since: 0.3.6
2263 * Deprecated: 0.9.6: Use as_node_context_set_format_kind() instead.
2264 **/
2265 void
as_node_context_set_source_kind(AsNodeContext * ctx,AsFormatKind source_kind)2266 as_node_context_set_source_kind (AsNodeContext *ctx, AsFormatKind source_kind)
2267 {
2268 return as_node_context_set_format_kind (ctx, source_kind);
2269 }
2270
2271 /**
2272 * as_node_context_get_output_trusted: (skip)
2273 * @ctx: a #AsNodeContext.
2274 *
2275 * Gets if the AppStream output is trusted to handle private data.
2276 *
2277 * Returns: output_trusted number
2278 *
2279 * Since: 0.5.5
2280 **/
2281 gboolean
as_node_context_get_output_trusted(AsNodeContext * ctx)2282 as_node_context_get_output_trusted (AsNodeContext *ctx)
2283 {
2284 return ctx->output_trusted;
2285 }
2286
2287 /**
2288 * as_node_context_set_output_trusted: (skip)
2289 * @ctx: a #AsNodeContext.
2290 * @output_trusted: boolean.
2291 *
2292 * Sets the AppStream output is trusted to handle private data.
2293 *
2294 * Since: 0.5.5
2295 **/
2296 void
as_node_context_set_output_trusted(AsNodeContext * ctx,gboolean output_trusted)2297 as_node_context_set_output_trusted (AsNodeContext *ctx, gboolean output_trusted)
2298 {
2299 ctx->output_trusted = output_trusted;
2300 }
2301
2302 /**
2303 * as_node_context_get_output: (skip)
2304 * @ctx: a #AsNodeContext.
2305 *
2306 * Gets the AppStream API destination kind used when inserting nodes.
2307 *
2308 * Returns: output format, e.g. %AS_FORMAT_KIND_APPDATA
2309 *
2310 * Since: 0.3.6
2311 **/
2312 AsFormatKind
as_node_context_get_output(AsNodeContext * ctx)2313 as_node_context_get_output (AsNodeContext *ctx)
2314 {
2315 return ctx->output;
2316 }
2317
2318 /**
2319 * as_node_context_set_output: (skip)
2320 * @ctx: a #AsNodeContext.
2321 * @output: an output kind, e.g. %AS_FORMAT_KIND_APPDATA
2322 *
2323 * Sets the AppStream API destination kind used when inserting nodes.
2324 *
2325 * Since: 0.3.6
2326 **/
2327 void
as_node_context_set_output(AsNodeContext * ctx,AsFormatKind output)2328 as_node_context_set_output (AsNodeContext *ctx, AsFormatKind output)
2329 {
2330 ctx->output = output;
2331 }
2332
2333 /**
2334 * as_node_context_get_media_base_url: (skip)
2335 * @ctx: a #AsNodeContext.
2336 *
2337 * Gets the base URL for media used when inserting nodes.
2338 *
2339 * Since: 0.5.11
2340 **/
2341 const gchar *
as_node_context_get_media_base_url(AsNodeContext * ctx)2342 as_node_context_get_media_base_url (AsNodeContext *ctx)
2343 {
2344 return ctx->media_base_url;
2345 }
2346
2347 /**
2348 * as_node_context_set_media_base_url: (skip)
2349 * @ctx: a #AsNodeContext.
2350 * @url: a URL
2351 *
2352 * Sets the base URL for media used when inserting nodes.
2353 *
2354 * Since: 0.5.11
2355 **/
2356 void
as_node_context_set_media_base_url(AsNodeContext * ctx,const gchar * url)2357 as_node_context_set_media_base_url (AsNodeContext *ctx, const gchar *url)
2358 {
2359 as_ref_string_assign_safe (&ctx->media_base_url, url);
2360 }
2361