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, "&amp;", '&');
261 	as_node_string_replace_inplace (data->cdata, "&lt;", '<');
262 	as_node_string_replace_inplace (data->cdata, "&gt;", '>');
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, "&", "&amp;");
281 		as_utils_string_replace (str, "<", "&lt;");
282 		as_utils_string_replace (str, ">", "&gt;");
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, "&", "&amp;");
330 		as_utils_string_replace (value_safe, "<", "&lt;");
331 		as_utils_string_replace (value_safe, ">", "&gt;");
332 		as_utils_string_replace (value_safe, "\"", "&quot;");
333 		as_utils_string_replace (value_safe, "'", "&apos;");
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