More validator improvements
[appstream:appstream.git] / src / as-metadata.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*-
2  *
3  * Copyright (C) 2012-2014 Matthias Klumpp <matthias@tenstral.net>
4  *
5  * Licensed under the GNU Lesser General Public License Version 3
6  *
7  * This library is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library.  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 /**
22  * SECTION:as-metadata
23  * @short_description: Parser for AppStream metadata
24  * @include: appstream.h
25  *
26  * This object parses AppStream metadata, including AppStream
27  * upstream metadata, which is defined by upstream projects.
28  * It returns an #AsComponent of the data.
29  *
30  * See also: #AsComponent, #AsDatabase
31  */
32
33 #include <config.h>
34 #include <glib.h>
35 #include <libxml/tree.h>
36 #include <libxml/parser.h>
37
38 #include "as-metadata.h"
39 #include "as-metadata-private.h"
40
41 #include "as-utils.h"
42 #include "as-utils-private.h"
43 #include "as-component.h"
44 #include "as-component-private.h"
45 #include "as-distro-details.h"
46
47 typedef struct _AsMetadataPrivate       AsMetadataPrivate;
48 struct _AsMetadataPrivate
49 {
50         gchar *locale;
51         AsParserMode mode;
52         gchar *origin_name;
53         gchar **icon_paths;
54 };
55
56 G_DEFINE_TYPE_WITH_PRIVATE (AsMetadata, as_metadata, G_TYPE_OBJECT)
57
58 #define GET_PRIVATE(o) (as_metadata_get_instance_private (o))
59
60 static gchar*           as_metadata_parse_value (AsMetadata* metad, xmlNode* node, gboolean translated);
61 static gchar**          as_metadata_get_children_as_array (AsMetadata* metad, xmlNode* node, const gchar* element_name);
62
63 /**
64  * as_metadata_finalize:
65  **/
66 static void
67 as_metadata_finalize (GObject *object)
68 {
69         AsMetadata *metad = AS_METADATA (object);
70         AsMetadataPrivate *priv = GET_PRIVATE (metad);
71
72         g_free (priv->locale);
73         g_strfreev (priv->icon_paths);
74         if (priv->origin_name != NULL)
75                 g_free (priv->origin_name);
76
77         G_OBJECT_CLASS (as_metadata_parent_class)->finalize (object);
78 }
79
80 /**
81  * as_metadata_init:
82  **/
83 static void
84 as_metadata_init (AsMetadata *metad)
85 {
86         const gchar * const *locale_names;
87         gchar *tmp;
88         AsMetadataPrivate *priv = GET_PRIVATE (metad);
89
90         locale_names = g_get_language_names ();
91         /* set active locale without UTF-8 suffix */
92         priv->locale = g_strdup (locale_names[0]);
93         tmp = g_strstr_len (priv->locale, -1, ".UTF-8");
94         if (tmp != NULL)
95                 *tmp = '\0';
96
97         priv->origin_name = NULL;
98         priv->icon_paths = as_distro_details_get_icon_repository_paths ();
99
100         priv->mode = AS_PARSER_MODE_UPSTREAM;
101 }
102
103 /**
104  * as_metadata_class_init:
105  **/
106 static void
107 as_metadata_class_init (AsMetadataClass *klass)
108 {
109         GObjectClass *object_class = G_OBJECT_CLASS (klass);
110         object_class->finalize = as_metadata_finalize;
111 }
112
113 /**
114  * as_metadata_error_quark:
115  *
116  * Return value: An error quark.
117  **/
118 GQuark
119 as_metadata_error_quark (void)
120 {
121         static GQuark quark = 0;
122         if (!quark)
123                 quark = g_quark_from_static_string ("AsMetadataError");
124         return quark;
125 }
126
127 static gchar*
128 as_metadata_parse_value (AsMetadata* metad, xmlNode* node, gboolean translated)
129 {
130         AsMetadataPrivate *priv;
131         gchar *content;
132         gchar *lang;
133         gchar *res;
134
135         g_return_val_if_fail (metad != NULL, NULL);
136         priv = GET_PRIVATE (metad);
137
138         content = (gchar*) xmlNodeGetContent (node);
139         lang = (gchar*) xmlGetProp (node, (xmlChar*) "lang");
140
141         if (translated) {
142                 gchar *current_locale;
143                 gchar **strv;
144                 gchar *str;
145                 /* FIXME: If not-localized generic node comes _after_ the localized ones,
146                  * the not-localized will override the localized. Wrong ordering should
147                  * not happen, but we should deal with that case anyway.
148                  */
149                 if (lang == NULL) {
150                         res = content;
151                         goto out;
152                 }
153                 current_locale = priv->locale;
154                 if (g_strcmp0 (lang, current_locale) == 0) {
155                         res = content;
156                         goto out;
157                 }
158                 strv = g_strsplit (current_locale, "_", 0);
159                 str = g_strdup (strv[0]);
160                 g_strfreev (strv);
161                 if (g_strcmp0 (lang, str) == 0) {
162                         res = content;
163                         g_free (str);
164                         goto out;
165                 }
166                 g_free (str);
167
168                 /* Haven't found a matching locale */
169                 res = NULL;
170                 g_free (content);
171                 goto out;
172         }
173         /* If we have a locale here, but want the untranslated item, return NULL */
174         if (lang != NULL) {
175                 res = NULL;
176                 g_free (content);
177                 goto out;
178         }
179         res = content;
180
181 out:
182         g_free (lang);
183         return res;
184 }
185
186 static gchar**
187 as_metadata_get_children_as_array (AsMetadata* metad, xmlNode* node, const gchar* element_name)
188 {
189         GPtrArray *list;
190         xmlNode *iter;
191         gchar **res;
192         g_return_val_if_fail (metad != NULL, NULL);
193         g_return_val_if_fail (element_name != NULL, NULL);
194         list = g_ptr_array_new_with_free_func (g_free);
195
196         for (iter = node->children; iter != NULL; iter = iter->next) {
197                 /* discard spaces */
198                 if (iter->type != XML_ELEMENT_NODE) {
199                                         continue;
200                 }
201                 if (g_strcmp0 ((gchar*) iter->name, element_name) == 0) {
202                         gchar* content = NULL;
203                         content = (gchar*) xmlNodeGetContent (iter);
204                         if (content != NULL) {
205                                 gchar *s;
206                                 s = as_string_strip (content);
207                                 g_ptr_array_add (list, s);
208                         }
209                         g_free (content);
210                 }
211         }
212
213         res = as_ptr_array_to_strv (list);
214         g_ptr_array_unref (list);
215         return res;
216 }
217
218
219 static void
220 as_metadata_process_screenshot (AsMetadata* metad, xmlNode* node, AsScreenshot* sshot)
221 {
222         xmlNode *iter;
223         gchar *node_name;
224         gchar *content = NULL;
225         g_return_if_fail (metad != NULL);
226         g_return_if_fail (sshot != NULL);
227
228         for (iter = node->children; iter != NULL; iter = iter->next) {
229                 /* discard spaces */
230                 if (iter->type != XML_ELEMENT_NODE)
231                         continue;
232
233                 node_name = (gchar*) iter->name;
234                 content = as_metadata_parse_value (metad, iter, TRUE);
235                 if (g_strcmp0 (node_name, "image") == 0) {
236                         AsImage *img;
237                         guint64 width;
238                         guint64 height;
239                         gchar *stype;
240                         gchar *str;
241                         if (content == NULL) {
242                                 continue;
243                         }
244                         img = as_image_new ();
245
246                         str = (gchar*) xmlGetProp (iter, (xmlChar*) "width");
247                         width = g_ascii_strtoll (str, NULL, 10);
248                         g_free (str);
249                         str = (gchar*) xmlGetProp (iter, (xmlChar*) "height");
250                         height = g_ascii_strtoll (str, NULL, 10);
251                         g_free (str);
252                         /* discard invalid elements */
253                         if ((width == 0) || (height == 0)) {
254                                 g_free (content);
255                                 continue;
256                         }
257
258                         as_image_set_width (img, width);
259                         as_image_set_height (img, height);
260
261                         stype = (gchar*) xmlGetProp (iter, (xmlChar*) "type");
262                         if (g_strcmp0 (stype, "thumbnail") == 0) {
263                                 as_image_set_kind (img, AS_IMAGE_KIND_THUMBNAIL);
264                         } else {
265                                 as_image_set_kind (img, AS_IMAGE_KIND_SOURCE);
266                         }
267                         g_free (stype);
268                         as_image_set_url (img, content);
269                         as_screenshot_add_image (sshot, img);
270                 } else if (g_strcmp0 (node_name, "caption") == 0) {
271                         if (content != NULL) {
272                                 as_screenshot_set_caption (sshot, content);
273                         }
274                 }
275                 g_free (content);
276         }
277 }
278
279 static void
280 as_metadata_process_screenshots_tag (AsMetadata* metad, xmlNode* node, AsComponent* cpt)
281 {
282         xmlNode *iter;
283         AsScreenshot *sshot = NULL;
284         gchar *prop;
285         g_return_if_fail (metad != NULL);
286         g_return_if_fail (cpt != NULL);
287
288         for (iter = node->children; iter != NULL; iter = iter->next) {
289                 /* discard spaces */
290                 if (iter->type != XML_ELEMENT_NODE)
291                         continue;
292
293                 if (g_strcmp0 ((gchar*) iter->name, "screenshot") == 0) {
294                         sshot = as_screenshot_new ();
295                         prop = (gchar*) xmlGetProp (iter, (xmlChar*) "type");
296                         if (g_strcmp0 (prop, "default") == 0)
297                                 as_screenshot_set_kind (sshot, AS_SCREENSHOT_KIND_DEFAULT);
298                         as_metadata_process_screenshot (metad, iter, sshot);
299                         if (as_screenshot_is_valid (sshot))
300                                 as_component_add_screenshot (cpt, sshot);
301                         g_free (prop);
302                         g_object_unref (sshot);
303                 }
304         }
305 }
306
307 static gchar*
308 as_metadata_parse_upstream_description_tag (AsMetadata* metad, xmlNode* node)
309 {
310         xmlNode *iter;
311         gchar *content;
312         gchar *node_name;
313         gchar *description_text;
314         g_return_if_fail (metad != NULL);
315
316         for (iter = node->children; iter != NULL; iter = iter->next) {
317                 gchar *tmp;
318                 /* discard spaces */
319                 if (iter->type != XML_ELEMENT_NODE)
320                         continue;
321
322                 node_name = (gchar*) iter->name;
323                 content = as_metadata_parse_value (metad, iter, TRUE);
324                 if (content == NULL)
325                         content = as_metadata_parse_value (metad, iter, TRUE);
326                 /* skip garbage */
327                 if (content == NULL)
328                         continue;
329
330                 tmp = g_strdup_printf ("%s\n<%s>%s</%s>", description_text, node_name, content, node_name);
331                 g_free (description_text);
332                 description_text = tmp;
333
334                 g_free (content);
335         }
336
337         return description_text;
338 }
339
340 static void
341 as_metadata_process_releases_tag (AsMetadata* metad, xmlNode* node, AsComponent* cpt)
342 {
343         xmlNode *iter;
344         xmlNode *iter2;
345         AsRelease *release = NULL;
346         gchar *prop;
347         guint64 timestamp;
348         AsMetadataPrivate *priv = GET_PRIVATE (metad);
349         g_return_if_fail (cpt != NULL);
350
351         for (iter = node->children; iter != NULL; iter = iter->next) {
352                 /* discard spaces */
353                 if (iter->type != XML_ELEMENT_NODE)
354                         continue;
355
356                 if (g_strcmp0 ((gchar*) iter->name, "release") == 0) {
357                         release = as_release_new ();
358
359                         prop = (gchar*) xmlGetProp (iter, (xmlChar*) "version");
360                         as_release_set_version (release, prop);
361                         g_free (prop);
362
363                         prop = (gchar*) xmlGetProp (iter, (xmlChar*) "timestamp");
364                         timestamp = g_ascii_strtoll (prop, NULL, 10);
365                         as_release_set_timestamp (release, timestamp);
366                         g_free (prop);
367
368                         for (iter2 = iter->children; iter2 != NULL; iter2 = iter2->next) {
369                                 if (iter->type != XML_ELEMENT_NODE)
370                                         continue;
371
372                                 if (g_strcmp0 ((gchar*) iter->name, "description") == 0) {
373                                         if (priv->mode == AS_PARSER_MODE_DISTRO) {
374                                                 gchar *content;
375                                                 /* for distros, the "description" tag has a language property, so parsing it is simple */
376                                                 content = as_metadata_parse_value (metad, iter2, FALSE);
377                                                 if (content == NULL)
378                                                         content = as_metadata_parse_value (metad, iter2, TRUE);
379                                                 if (content != NULL)
380                                                         as_release_set_description (release, content);
381                                                 g_free (content);
382                                                 break;
383                                         } else {
384                                                 gchar *text;
385                                                 text = as_metadata_parse_upstream_description_tag (metad, iter2);
386                                                 as_release_set_description (release, text);
387                                                 g_free (text);
388                                                 break;
389                                         }
390                                 }
391                         }
392
393                         as_component_add_release (cpt, release);
394                         g_object_unref (release);
395                 }
396         }
397 }
398
399 static void
400 as_metadata_process_provides (AsMetadata* metad, xmlNode* node, AsComponent* cpt)
401 {
402         xmlNode *iter;
403         gchar *node_name;
404         gchar *content = NULL;
405         GPtrArray *provided_items;
406         g_return_if_fail (metad != NULL);
407         g_return_if_fail (cpt != NULL);
408
409         provided_items = as_component_get_provided_items (cpt);
410         for (iter = node->children; iter != NULL; iter = iter->next) {
411                 /* discard spaces */
412                 if (iter->type != XML_ELEMENT_NODE)
413                         continue;
414
415                 node_name = (gchar*) iter->name;
416                 content = as_metadata_parse_value (metad, iter, TRUE);
417                 if (content == NULL)
418                         continue;
419
420                 if (g_strcmp0 (node_name, "library") == 0) {
421                         g_ptr_array_add (provided_items,
422                                                          as_provides_item_create (AS_PROVIDES_KIND_LIBRARY, content, ""));
423                 } else if (g_strcmp0 (node_name, "binary") == 0) {
424                         g_ptr_array_add (provided_items,
425                                                          as_provides_item_create (AS_PROVIDES_KIND_BINARY, content, ""));
426                 } else if (g_strcmp0 (node_name, "font") == 0) {
427                         g_ptr_array_add (provided_items,
428                                                          as_provides_item_create (AS_PROVIDES_KIND_FONT, content, ""));
429                 } else if (g_strcmp0 (node_name, "modalias") == 0) {
430                         g_ptr_array_add (provided_items,
431                                                          as_provides_item_create (AS_PROVIDES_KIND_MODALIAS, content, ""));
432                 } else if (g_strcmp0 (node_name, "firmware") == 0) {
433                         g_ptr_array_add (provided_items,
434                                                          as_provides_item_create (AS_PROVIDES_KIND_FIRMWARE, content, ""));
435                 } else if (g_strcmp0 (node_name, "python2") == 0) {
436                         g_ptr_array_add (provided_items,
437                                                          as_provides_item_create (AS_PROVIDES_KIND_PYTHON2, content, ""));
438                 } else if (g_strcmp0 (node_name, "python3") == 0) {
439                         g_ptr_array_add (provided_items,
440                                                          as_provides_item_create (AS_PROVIDES_KIND_PYTHON3, content, ""));
441                 }
442                 g_free (content);
443         }
444 }
445
446 /**
447  * as_metadata_refine_component_icon:
448  *
449  * We use this method to ensure the "icon" and "icon_url" properties of
450  * a component are properly set, by finding the icons in default directories.
451  */
452 static void
453 as_metadata_refine_component_icon (AsMetadata *metad, AsComponent *cpt)
454 {
455         const gchar *exensions[] = { "png",
456                                      "svg",
457                                      "svgz",
458                                      "gif",
459                                      "ico",
460                                      "xcf",
461                                      NULL };
462         gchar *tmp_icon_path;
463         const gchar *icon_url;
464         guint i, j;
465         AsMetadataPrivate *priv = GET_PRIVATE (metad);
466
467         icon_url = as_component_get_icon_url (cpt);
468         if (g_str_has_prefix (icon_url, "/") ||
469                 g_str_has_prefix (icon_url, "http://")) {
470                 /* looks like this component already has a full icon path... */
471                 return;
472         }
473         tmp_icon_path = NULL;
474
475         if (g_strcmp0 (icon_url, "") == 0) {
476                 icon_url = as_component_get_icon (cpt);
477         }
478
479         /* search local icon path */
480         for (i = 0; priv->icon_paths[i] != NULL; i++) {
481                 /* sometimes, the file already has an extension */
482                 tmp_icon_path = g_strdup_printf ("%s/%s",
483                                              priv->icon_paths[i],
484                                              icon_url);
485                 if (g_file_test (tmp_icon_path, G_FILE_TEST_EXISTS))
486                         goto out;
487                 g_free (tmp_icon_path);
488
489                 /* file not found, try extensions (we will not do this forever, better fix AppStream data!) */
490                 for (j = 0; exensions[j] != NULL; j++) {
491                         tmp_icon_path = g_strdup_printf ("%s/%s.%s",
492                                              priv->icon_paths[i],
493                                              icon_url,
494                                              exensions[j]);
495                         if (g_file_test (tmp_icon_path, G_FILE_TEST_EXISTS))
496                                 goto out;
497                         g_free (tmp_icon_path);
498                         tmp_icon_path = NULL;
499                 }
500         }
501
502 out:
503         if (tmp_icon_path != NULL) {
504                 as_component_set_icon_url (cpt, tmp_icon_path);
505                 g_free (tmp_icon_path);
506         }
507 }
508
509 AsComponent*
510 as_metadata_parse_component_node (AsMetadata* metad, xmlNode* node, GError **error)
511 {
512         AsComponent* cpt;
513         xmlNode *iter;
514         const gchar *node_name;
515         gchar *content;
516         GPtrArray *compulsory_for_desktops;
517         gchar **strv;
518         gchar *cpttype;
519         AsMetadataPrivate *priv = GET_PRIVATE (metad);
520
521         g_return_if_fail (metad != NULL);
522
523         compulsory_for_desktops = g_ptr_array_new_with_free_func (g_free);
524
525         /* a fresh app component */
526         cpt = as_component_new ();
527
528         /* find out which kind of component we are dealing with */
529         cpttype = (gchar*) xmlGetProp (node, (xmlChar*) "type");
530         if ((cpttype == NULL) || (g_strcmp0 (cpttype, "generic") == 0)) {
531                 as_component_set_kind (cpt, AS_COMPONENT_KIND_GENERIC);
532         } else {
533                 AsComponentKind ckind;
534                 ckind = as_component_kind_from_string (cpttype);
535                 as_component_set_kind (cpt, ckind);
536                 if (ckind == AS_COMPONENT_KIND_UNKNOWN)
537                         g_debug ("An unknown component was found: %s", cpttype);
538         }
539         g_free (cpttype);
540
541         if (priv->mode == AS_PARSER_MODE_DISTRO) {
542                 /* distro metadata allows setting a priority for components */
543                 gchar *priority_str;
544                 priority_str = (gchar*) xmlGetProp (node, (xmlChar*) "priority");
545                 if (priority_str != NULL) {
546                         int priority;
547                         priority = g_ascii_strtoll (priority_str, NULL, 10);
548                         as_component_set_priority (cpt, priority);
549                 }
550                 g_free (priority_str);
551         }
552
553         for (iter = node->children; iter != NULL; iter = iter->next) {
554                 /* discard spaces */
555                 if (iter->type != XML_ELEMENT_NODE)
556                         continue;
557                 node_name = (const gchar*) iter->name;
558                 content = as_metadata_parse_value (metad, iter, FALSE);
559                 if (g_strcmp0 (node_name, "id") == 0) {
560                                 as_component_set_idname (cpt, content);
561                 } else if (g_strcmp0 (node_name, "pkgname") == 0) {
562                         if (content != NULL)
563                                 as_component_set_pkgname (cpt, content);
564                 } else if (g_strcmp0 (node_name, "name") == 0) {
565                         if (content != NULL) {
566                                 as_component_set_name_original (cpt, content);
567                         } else {
568                                 content = as_metadata_parse_value (metad, iter, TRUE);
569                                 if (content != NULL)
570                                         as_component_set_name (cpt, content);
571                         }
572                 } else if (g_strcmp0 (node_name, "summary") == 0) {
573                         if (content != NULL) {
574                                 as_component_set_summary (cpt, content);
575                         } else {
576                                 content = as_metadata_parse_value (metad, iter, TRUE);
577                                 if (content != NULL)
578                                         as_component_set_summary (cpt, content);
579                         }
580                 } else if (g_strcmp0 (node_name, "description") == 0) {
581                         if (priv->mode == AS_PARSER_MODE_DISTRO) {
582                                 /* for distros, the "description" tag has a language property, so parsing it is simple */
583                                 if (content != NULL) {
584                                         as_component_set_description (cpt, content);
585                                 } else {
586                                         content = as_metadata_parse_value (metad, iter, TRUE);
587                                         if (content != NULL)
588                                                 as_component_set_description (cpt, content);
589                                 }
590                         } else {
591                                 gchar *text;
592                                 text = as_metadata_parse_upstream_description_tag (metad, iter);
593                                 as_component_set_description (cpt, text);
594                                 g_free (text);
595                         }
596                 } else if (g_strcmp0 (node_name, "icon") == 0) {
597                         gchar *prop;
598                         const gchar *icon_url;
599                         if (content == NULL)
600                                 continue;
601                         prop = (gchar*) xmlGetProp (iter, (xmlChar*) "type");
602                         if (g_strcmp0 (prop, "stock") == 0) {
603                                 as_component_set_icon (cpt, content);
604                         } else if (g_strcmp0 (prop, "cached") == 0) {
605                                 icon_url = as_component_get_icon_url (cpt);
606                                 if ((g_strcmp0 (icon_url, "") == 0) || (g_str_has_prefix (icon_url, "http://"))) {
607                                         gchar *icon_path_part;
608                                         /* prepend the origin, to have canonical paths later */
609                                         if (priv->origin_name == NULL)
610                                                 icon_path_part = g_strdup (content);
611                                         else
612                                                 icon_path_part = g_strdup_printf ("%s/%s", priv->origin_name, content);
613                                         as_component_set_icon_url (cpt, icon_path_part);
614                                         g_free (icon_path_part);
615                                 }
616                         } else if (g_strcmp0 (prop, "local") == 0) {
617                                 as_component_set_icon_url (cpt, content);
618                         } else if (g_strcmp0 (prop, "remote") == 0) {
619                                 icon_url = as_component_get_icon_url (cpt);
620                                 if (g_strcmp0 (icon_url, "") == 0)
621                                         as_component_set_icon_url (cpt, content);
622                         }
623                 } else if (g_strcmp0 (node_name, "url") == 0) {
624                         if (content != NULL) {
625                                 gchar *urltype_str;
626                                 AsUrlKind url_kind;
627                                 urltype_str = (gchar*) xmlGetProp (iter, (xmlChar*) "type");
628                                 url_kind = as_url_kind_from_string (urltype_str);
629                                 if (url_kind != AS_URL_KIND_UNKNOWN)
630                                         as_component_add_url (cpt, url_kind, content);
631                                 g_free (urltype_str);
632                         }
633                 } else if (g_strcmp0 (node_name, "categories") == 0) {
634                         gchar **cat_array;
635                         cat_array = as_metadata_get_children_as_array (metad, iter, "category");
636                         as_component_set_categories (cpt, cat_array);
637                 } else if (g_strcmp0 (node_name, "provides") == 0) {
638                         as_metadata_process_provides (metad, iter, cpt);
639                 } else if (g_strcmp0 (node_name, "screenshots") == 0) {
640                         as_metadata_process_screenshots_tag (metad, iter, cpt);
641                 } else if (g_strcmp0 (node_name, "project_license") == 0) {
642                         if (content != NULL)
643                                 as_component_set_project_license (cpt, content);
644                 } else if (g_strcmp0 (node_name, "project_group") == 0) {
645                         if (content != NULL)
646                                 as_component_set_project_group (cpt, content);
647                 } else if (g_strcmp0 (node_name, "compulsory_for_desktop") == 0) {
648                         if (content != NULL)
649                                 g_ptr_array_add (compulsory_for_desktops, g_strdup (content));
650                 } else if (g_strcmp0 (node_name, "releases") == 0) {
651                         as_metadata_process_releases_tag (metad, iter, cpt);
652                 }
653                 g_free (content);
654         }
655
656         /* add compulsory information to component as strv */
657         strv = as_ptr_array_to_strv (compulsory_for_desktops);
658         as_component_set_compulsory_for_desktops (cpt, strv);
659         g_ptr_array_unref (compulsory_for_desktops);
660         g_strfreev (strv);
661
662         if (as_component_is_valid (cpt)) {
663                 /* find local icon on the filesystem */
664                 as_metadata_refine_component_icon (metad, cpt);
665
666                 return cpt;
667         } else {
668                 gchar *cpt_str;
669                 gchar *msg;
670                 cpt_str = as_component_to_string (cpt);
671                 msg = g_strdup_printf ("Invalid component: %s", cpt_str);
672                 g_free (cpt_str);
673                 g_set_error_literal (error,
674                                      AS_METADATA_ERROR,
675                                      AS_METADATA_ERROR_FAILED,
676                                      msg);
677                 g_free (msg);
678                 g_object_unref (cpt);
679         }
680
681         return NULL;
682 }
683
684 static AsComponent*
685 as_metadata_process_document (AsMetadata *metad, const gchar* xmldoc_str, GError **error)
686 {
687         xmlDoc* doc;
688         xmlNode* root;
689         AsComponent *cpt = NULL;
690
691         g_return_val_if_fail (metad != NULL, FALSE);
692         g_return_val_if_fail (xmldoc_str != NULL, FALSE);
693
694         doc = xmlParseDoc ((xmlChar*) xmldoc_str);
695         if (doc == NULL) {
696                 g_set_error_literal (error,
697                                      AS_METADATA_ERROR,
698                                      AS_METADATA_ERROR_FAILED,
699                                      "Could not parse XML!");
700                 return NULL;
701         }
702
703         root = xmlDocGetRootElement (doc);
704         if (doc == NULL) {
705                 g_set_error_literal (error,
706                                      AS_METADATA_ERROR,
707                                      AS_METADATA_ERROR_FAILED,
708                                      "The XML document is empty.");
709                 return NULL;
710         }
711
712         if (g_strcmp0 ((gchar*) root->name, "component") != 0) {
713                 g_set_error_literal (error,
714                                      AS_METADATA_ERROR,
715                                      AS_METADATA_ERROR_FAILED,
716                                      "XML file does not contain valid AppStream data!");
717                 goto out;
718         }
719
720         cpt = as_metadata_parse_component_node (metad, root, error);
721
722 out:
723         xmlFreeDoc (doc);
724
725         return cpt;
726 }
727
728 /**
729  * as_metadata_parse_data:
730  * @metad: A valid #AsMetadata instance
731  * @data: XML data describing a component
732  * @error: A #GError or %NULL.
733  *
734  * Parses AppStream upstream metadata.
735  *
736  * Returns: (transfer full): the #AsComponent of this data, or NULL on error
737  **/
738 AsComponent*
739 as_metadata_parse_data (AsMetadata* metad, const gchar *data, GError **error)
740 {
741         AsComponent *cpt;
742         g_return_val_if_fail (metad != NULL, NULL);
743         g_return_val_if_fail (data != NULL, NULL);
744
745         cpt = as_metadata_process_document (metad, data, error);
746
747         return cpt;
748 }
749
750 /**
751  * as_metadata_parse_file:
752  * @metad: A valid #AsMetadata instance
753  * @infile: #GFile for the upstream metadata
754  * @error: A #GError or %NULL.
755  *
756  * Parses an AppStream upstream metadata file.
757  *
758  * Returns: (transfer full): the #AsComponent of this file, or NULL on error
759  **/
760 AsComponent*
761 as_metadata_parse_file (AsMetadata* metad, GFile* infile, GError **error)
762 {
763         AsComponent *cpt;
764         gchar* xml_doc;
765         gchar* line = NULL;
766         GFileInputStream* ir;
767         GDataInputStream* dis;
768
769         g_return_val_if_fail (metad != NULL, NULL);
770         g_return_val_if_fail (infile != NULL, NULL);
771
772         xml_doc = g_strdup ("");
773         ir = g_file_read (infile, NULL, NULL);
774         dis = g_data_input_stream_new ((GInputStream*) ir);
775         g_object_unref (ir);
776
777         while (TRUE) {
778                 gchar *str;
779                 gchar *tmp;
780
781                 line = g_data_input_stream_read_line (dis, NULL, NULL, NULL);
782                 if (line == NULL) {
783                         break;
784                 }
785
786                 str = g_strconcat (line, "\n", NULL);
787                 g_free (line);
788                 tmp = g_strconcat (xml_doc, str, NULL);
789                 g_free (str);
790                 g_free (xml_doc);
791                 xml_doc = tmp;
792         }
793
794         cpt = as_metadata_process_document (metad, xml_doc, error);
795         g_object_unref (dis);
796         g_free (xml_doc);
797
798         return cpt;
799 }
800
801 /**
802  * as_metadata_set_locale:
803  * @metad: a #AsMezadata instance.
804  * @locale: the locale.
805  *
806  * Sets the current locale whcih should be used when parsing metadata.
807  **/
808 void
809 as_metadata_set_locale (AsMetadata *metad, const gchar *locale)
810 {
811         AsMetadataPrivate *priv = GET_PRIVATE (metad);
812         g_free (priv->locale);
813         priv->locale = g_strdup (locale);
814 }
815
816 /**
817  * as_metadata_get_locale:
818  * @metad: a #AsMetadata instance.
819  *
820  * Gets the currently used locale.
821  *
822  * Returns: Locale used for metadata parsing.
823  **/
824 const gchar *
825 as_metadata_get_locale (AsMetadata *metad)
826 {
827         AsMetadataPrivate *priv = GET_PRIVATE (metad);
828         return priv->locale;
829 }
830
831 /**
832  * as_metadata_set_origin_id:
833  * @metad: a #AsMetadata instance.
834  * @origin: the origin of AppStream distro metadata.
835  *
836  * Internal method to set the origin of AppStream distro metadata
837  **/
838 void
839 as_metadata_set_origin_id (AsMetadata *metad, const gchar *origin)
840 {
841         AsMetadataPrivate *priv = GET_PRIVATE (metad);
842         g_free (priv->origin_name);
843         priv->origin_name = g_strdup (origin);
844 }
845
846 /**
847  * as_metadata_set_parser_mode:
848  * @metad: a #AsMezadata instance.
849  * @mode: the #AsParserMode.
850  *
851  * Sets the current metadata parsing mode.
852  **/
853 void
854 as_metadata_set_parser_mode (AsMetadata *metad, AsParserMode mode)
855 {
856         AsMetadataPrivate *priv = GET_PRIVATE (metad);
857         priv->mode = mode;
858 }
859
860 /**
861  * as_metadata_get_parser_mode:
862  * @metad: a #AsMezadata instance.
863  *
864  * Gets the current parser mode
865  *
866  * Returns: an #AsParserMode
867  **/
868 AsParserMode
869 as_metadata_get_parser_mode (AsMetadata *metad)
870 {
871         AsMetadataPrivate *priv = GET_PRIVATE (metad);
872         return priv->mode;
873 }
874
875 /**
876  * as_metadata_new:
877  *
878  * Creates a new #AsMetadata.
879  *
880  * Returns: (transfer full): a #AsMetadata
881  **/
882 AsMetadata*
883 as_metadata_new (void)
884 {
885         AsMetadata *metad;
886         metad = g_object_new (AS_TYPE_METADATA, NULL);
887         return AS_METADATA (metad);
888 }