4 * Parses AppSteam Data files.
5 * See http://people.freedesktop.org/~hughsient/appdata/
8 * Copyright (c) 2013, Novell Inc.
10 * This program is licensed under the BSD license, read LICENSE.BSD
11 * for further information
14 #include <sys/types.h>
31 #include "repo_appdata.h"
63 /* !! must be sorted by first column !! */
64 static struct stateswitch stateswitches[] = {
65 { STATE_START, "applications", STATE_START, 0 },
66 { STATE_START, "components", STATE_START, 0 },
67 { STATE_START, "application", STATE_APPLICATION, 0 },
68 { STATE_START, "component", STATE_APPLICATION, 0 },
69 { STATE_APPLICATION, "id", STATE_ID, 1 },
70 { STATE_APPLICATION, "pkgname", STATE_PKGNAME, 1 },
71 { STATE_APPLICATION, "product_license", STATE_LICENCE, 1 },
72 { STATE_APPLICATION, "name", STATE_NAME, 1 },
73 { STATE_APPLICATION, "summary", STATE_SUMMARY, 1 },
74 { STATE_APPLICATION, "description", STATE_DESCRIPTION, 0 },
75 { STATE_APPLICATION, "url", STATE_URL, 1 },
76 { STATE_APPLICATION, "project_group", STATE_GROUP, 1 },
77 { STATE_APPLICATION, "keywords", STATE_KEYWORDS, 0 },
78 { STATE_APPLICATION, "extends", STATE_EXTENDS, 1 },
79 { STATE_DESCRIPTION, "p", STATE_P, 1 },
80 { STATE_DESCRIPTION, "ul", STATE_UL, 0 },
81 { STATE_DESCRIPTION, "ol", STATE_OL, 0 },
82 { STATE_UL, "li", STATE_UL_LI, 1 },
83 { STATE_OL, "li", STATE_OL_LI, 1 },
84 { STATE_KEYWORDS, "keyword", STATE_KEYWORD, 1 },
100 struct stateswitch *swtab[NUMSTATES];
101 enum state sbtab[NUMSTATES];
112 const char *filename;
117 static inline const char *
118 find_attr(const char *txt, const char **atts)
120 for (; *atts; atts += 2)
121 if (!strcmp(*atts, txt))
128 startElement(void *userData, const char *name, const char **atts)
130 struct parsedata *pd = userData;
131 Pool *pool = pd->pool;
132 Solvable *s = pd->solvable;
133 struct stateswitch *sw;
137 fprintf(stderr, "start: [%d]%s\n", pd->state, name);
139 if (pd->depth != pd->statedepth)
146 if (!pd->swtab[pd->state]) /* no statetable -> no substates */
149 fprintf(stderr, "into unknown: %s (from: %d)\n", name, pd->state);
153 for (sw = pd->swtab[pd->state]; sw->from == pd->state; sw++) /* find name in statetable */
154 if (!strcmp(sw->ename, name))
157 if (sw->from != pd->state)
160 fprintf(stderr, "into unknown: %s (from: %d)\n", name, pd->state);
165 pd->docontent = sw->docontent;
166 pd->statedepth = pd->depth;
170 if (!pd->skip_depth && find_attr("xml:lang", atts))
171 pd->skip_depth = pd->depth;
180 case STATE_APPLICATION:
181 type = find_attr("type", atts);
184 if (strcmp(type, "desktop") != 0)
190 s = pd->solvable = pool_id2solvable(pool, repo_add_solvable(pd->repo));
191 pd->handle = s - pool->solvables;
193 repodata_set_poolstr(pd->data, pd->handle, SOLVABLE_CATEGORY, type);
195 case STATE_DESCRIPTION:
196 pd->description = solv_free(pd->description);
207 /* replace whitespace with one space/newline */
208 /* also strip starting/ending whitespace */
210 wsstrip(struct parsedata *pd)
214 for (i = j = 0; pd->content[i]; i++)
216 if (pd->content[i] == ' ' || pd->content[i] == '\t' || pd->content[i] == '\n')
218 ws |= pd->content[i] == '\n' ? 2 : 1;
222 pd->content[j++] = (ws & 2) ? '\n' : ' ';
224 pd->content[j++] = pd->content[i];
230 /* indent all lines */
232 indent(struct parsedata *pd, int il)
235 for (l = 0; pd->content[l]; )
237 if (pd->content[l] == '\n')
242 if (pd->lcontent + il + 1 > pd->acontent)
244 pd->acontent = pd->lcontent + il + 256;
245 pd->content = realloc(pd->content, pd->acontent);
247 memmove(pd->content + l + il, pd->content + l, pd->lcontent - l + 1);
248 for (i = 0; i < il; i++)
249 pd->content[l + i] = ' ';
251 while (pd->content[l] && pd->content[l] != '\n')
257 add_missing_tags_from_desktop_file(struct parsedata *pd, Solvable *s, const char *desktop_file)
259 Pool *pool = pd->pool;
261 const char *filepath;
266 filepath = pool_tmpjoin(pool, "/usr/share/applications/", desktop_file, 0);
267 if (pd->flags & REPO_USE_ROOTDIR)
268 filepath = pool_prepend_rootdir_tmp(pool, filepath);
269 if (!(fp = fopen(filepath, "r")))
271 while (fgets(buf, sizeof(buf), fp) > 0)
273 int c, l = strlen(buf);
276 if (buf[l - 1] != '\n')
278 /* ignore overlong lines */
279 while ((c = getc(fp)) != EOF)
287 while (l && (buf[l - 1] == ' ' || buf[l - 1] == '\t'))
290 while (*p == ' ' || *p == '\t')
292 if (!*p || *p == '#')
296 if (!strcmp(p, "[Desktop Entry]"))
307 for (p3 = p2 - 1; *p3 == ' ' || *p3 == '\t'; p3--)
310 while (*p2 == ' ' || *p2 == '\t')
314 if (!s->name && !strcmp(p, "Name"))
315 s->name = pool_str2id(pool, pool_tmpjoin(pool, "application:", p2, 0), 1);
316 else if (!pd->havesummary && !strcmp(p, "Comment"))
319 repodata_set_str(pd->data, pd->handle, SOLVABLE_SUMMARY, p2);
323 if (s->name && pd->havesummary)
324 break; /* our work is done */
330 guess_filename_from_id(Pool *pool, const char *id)
333 char *r = pool_tmpjoin(pool, id, ".metainfo.xml", 0);
334 if (l > 8 && !strcmp(".desktop", id + l - 8))
335 strcpy(r + l - 8, ".appdata.xml");
336 else if (l > 4 && !strcmp(".ttf", id + l - 4))
337 strcpy(r + l - 4, ".metainfo.xml");
338 else if (l > 4 && !strcmp(".otf", id + l - 4))
339 strcpy(r + l - 4, ".metainfo.xml");
340 else if (l > 4 && !strcmp(".xml", id + l - 4))
341 strcpy(r + l - 4, ".metainfo.xml");
342 else if (l > 3 && !strcmp(".db", id + l - 3))
343 strcpy(r + l - 3, ".metainfo.xml");
350 endElement(void *userData, const char *name)
352 struct parsedata *pd = userData;
353 Pool *pool = pd->pool;
354 Solvable *s = pd->solvable;
358 fprintf(stderr, "end: [%d]%s\n", pd->state, name);
360 if (pd->depth != pd->statedepth)
364 fprintf(stderr, "back from unknown %d %d %d\n", pd->state, pd->depth, pd->statedepth);
372 if (pd->skip_depth && pd->depth + 1 >= pd->skip_depth)
374 if (pd->depth + 1 == pd->skip_depth)
376 pd->state = pd->sbtab[pd->state];
384 pd->state = pd->sbtab[pd->state];
391 case STATE_APPLICATION:
393 s->arch = ARCH_NOARCH;
396 if ((!s->name || !pd->havesummary) && (pd->flags & APPDATA_CHECK_DESKTOP_FILE) != 0 && pd->desktop_file)
397 add_missing_tags_from_desktop_file(pd, s, pd->desktop_file);
398 if (!s->name && pd->desktop_file)
400 char *name = pool_tmpjoin(pool, "application:", pd->desktop_file, 0);
401 int l = strlen(name);
402 if (l > 8 && !strcmp(".desktop", name + l - 8))
404 s->name = pool_strn2id(pool, name, l, 1);
406 if (!s->requires && pd->owners)
410 for (i = 0; i < pd->owners->count; i++)
412 Solvable *os = pd->pool->solvables + pd->owners->elements[i];
413 s->requires = repo_addid_dep(pd->repo, s->requires, os->name, 0);
414 id = pool_str2id(pd->pool, pool_tmpjoin(pd->pool, "application-appdata(", pool_id2str(pd->pool, os->name), ")"), 1);
415 s->provides = repo_addid_dep(pd->repo, s->provides, id, 0);
418 if (!s->requires && (pd->desktop_file || pd->filename))
420 /* add appdata() link requires/provides */
421 const char *filename = pd->filename;
423 filename = guess_filename_from_id(pool, pd->desktop_file);
426 filename = pool_tmpjoin(pool, "application-appdata(", filename, ")");
427 s->requires = repo_addid_dep(pd->repo, s->requires, pool_str2id(pd->pool, filename + 12, 1), 0);
428 s->provides = repo_addid_dep(pd->repo, s->provides, pool_str2id(pd->pool, filename, 1), 0);
431 if (s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC)
432 s->provides = repo_addid_dep(pd->repo, s->provides, pool_rel2id(pd->pool, s->name, s->evr, REL_EQ, 1), 0);
434 pd->desktop_file = solv_free(pd->desktop_file);
437 pd->desktop_file = solv_strdup(pd->content);
440 s->name = pool_str2id(pd->pool, pool_tmpjoin(pool, "application:", pd->content, 0), 1);
443 repodata_add_poolstr_array(pd->data, pd->handle, SOLVABLE_LICENSE, pd->content);
447 repodata_set_str(pd->data, pd->handle, SOLVABLE_SUMMARY, pd->content);
450 repodata_set_str(pd->data, pd->handle, SOLVABLE_URL, pd->content);
453 repodata_add_poolstr_array(pd->data, pd->handle, SOLVABLE_GROUP, pd->content);
456 repodata_add_poolstr_array(pd->data, pd->handle, SOLVABLE_EXTENDS, pd->content);
458 case STATE_DESCRIPTION:
461 /* strip trailing newlines */
462 int l = strlen(pd->description);
463 while (l && pd->description[l - 1] == '\n')
464 pd->description[--l] = 0;
465 repodata_set_str(pd->data, pd->handle, SOLVABLE_DESCRIPTION, pd->description);
470 pd->description = solv_dupappend(pd->description, pd->content, "\n\n");
475 pd->content[2] = '-';
476 pd->description = solv_dupappend(pd->description, pd->content, "\n");
481 if (++pd->licnt >= 10)
482 pd->content[0] = '0' + (pd->licnt / 10) % 10;
483 pd->content[1] = '0' + pd->licnt % 10;
484 pd->content[2] = '.';
485 pd->description = solv_dupappend(pd->description, pd->content, "\n");
489 pd->description = solv_dupappend(pd->description, "\n", 0);
492 id = pool_str2id(pd->pool, pd->content, 1);
493 s->requires = repo_addid_dep(pd->repo, s->requires, id, 0);
494 id = pool_str2id(pd->pool, pool_tmpjoin(pd->pool, "application-appdata(", pd->content, ")"), 1);
495 s->provides = repo_addid_dep(pd->repo, s->provides, id, 0);
498 repodata_add_poolstr_array(pd->data, pd->handle, SOLVABLE_KEYWORDS, pd->content);
504 pd->state = pd->sbtab[pd->state];
508 fprintf(stderr, "end: [%s] -> %d\n", name, pd->state);
514 characterData(void *userData, const XML_Char *s, int len)
516 struct parsedata *pd = userData;
521 l = pd->lcontent + len + 1;
522 if (l > pd->acontent)
524 pd->acontent = l + 256;
525 pd->content = realloc(pd->content, pd->acontent);
527 c = pd->content + pd->lcontent;
534 #define BUFF_SIZE 8192
537 repo_add_appdata_fn(Repo *repo, FILE *fp, int flags, const char *filename, Queue *owners)
539 Pool *pool = repo->pool;
541 struct stateswitch *sw;
547 data = repo_add_repodata(repo, flags);
548 memset(&pd, 0, sizeof(pd));
550 pd.pool = repo->pool;
553 pd.filename = filename;
556 pd.content = malloc(256);
559 for (i = 0, sw = stateswitches; sw->from != NUMSTATES; i++, sw++)
561 if (!pd.swtab[sw->from])
562 pd.swtab[sw->from] = sw;
563 pd.sbtab[sw->to] = sw->from;
566 XML_Parser parser = XML_ParserCreate(NULL);
567 XML_SetUserData(parser, &pd);
568 XML_SetElementHandler(parser, startElement, endElement);
569 XML_SetCharacterDataHandler(parser, characterData);
573 l = fread(buf, 1, sizeof(buf), fp);
574 if (XML_Parse(parser, buf, l, l == 0) == XML_STATUS_ERROR)
576 pool_error(pool, -1, "repo_appdata: %s at line %u:%u\n", XML_ErrorString(XML_GetErrorCode(parser)), (unsigned int)XML_GetCurrentLineNumber(parser), (unsigned int)XML_GetCurrentColumnNumber(parser));
579 repo_free_solvable(repo, pd.solvable - pd.pool->solvables, 1);
588 XML_ParserFree(parser);
590 if (!(flags & REPO_NO_INTERNALIZE))
591 repodata_internalize(data);
593 solv_free(pd.content);
594 solv_free(pd.desktop_file);
595 solv_free(pd.description);
600 repo_add_appdata(Repo *repo, FILE *fp, int flags)
602 return repo_add_appdata_fn(repo, fp, flags, 0, 0);
606 search_uninternalized_filelist(Repo *repo, const char *dir, Queue *res)
608 Pool *pool = repo->pool;
612 for (rdid = 1; rdid < repo->nrepodata; rdid++)
614 Repodata *data = repo_id2repodata(repo, rdid);
617 if (data->state == REPODATA_STUB)
619 if (!repodata_has_keyname(data, SOLVABLE_FILELIST))
621 did = repodata_str2dir(data, dir, 0);
624 for (p = data->start; p < data->end; p++)
626 if (p >= pool->nsolvables)
628 if (pool->solvables[p].repo != repo)
637 str = repodata_lookup_dirstrarray_uninternalized(data, p, SOLVABLE_FILELIST, &idid, &iter);
641 if (l > 12 && strncmp(str + l - 12, ".appdata.xml", 12))
642 id = pool_str2id(pool, str, 1);
643 else if (l > 13 && strncmp(str + l - 13, ".metainfo.xml", 13))
644 id = pool_str2id(pool, str, 1);
647 queue_push2(res, p, id);
653 /* add all files ending in .appdata.xml */
655 repo_add_appdata_dir(Repo *repo, const char *appdatadir, int flags)
665 if (flags & APPDATA_SEARCH_UNINTERNALIZED_FILELIST)
666 search_uninternalized_filelist(repo, appdatadir, &flq);
667 data = repo_add_repodata(repo, flags);
668 if (flags & REPO_USE_ROOTDIR)
669 dirpath = pool_prepend_rootdir(repo->pool, appdatadir);
671 dirpath = solv_strdup(appdatadir);
672 if ((dir = opendir(dirpath)) != 0)
674 struct dirent *entry;
675 while ((entry = readdir(dir)))
679 int len = strlen(entry->d_name);
680 if (entry->d_name[0] == '.')
682 if (!(len > 12 && !strcmp(entry->d_name + len - 12, ".appdata.xml")) &&
683 !(len > 13 && !strcmp(entry->d_name + len - 13, ".metainfo.xml")))
685 n = pool_tmpjoin(repo->pool, dirpath, "/", entry->d_name);
689 pool_error(repo->pool, 0, "%s: %s", n, strerror(errno));
692 if (flags & APPDATA_SEARCH_UNINTERNALIZED_FILELIST)
694 Id id = pool_str2id(repo->pool, entry->d_name, 0);
699 for (i = 0; i < flq.count; i += 2)
700 if (flq.elements[i + 1] == id)
701 queue_push(&oq, flq.elements[i]);
704 repo_add_appdata_fn(repo, fp, flags | REPO_NO_INTERNALIZE | REPO_REUSE_REPODATA | APPDATA_CHECK_DESKTOP_FILE, entry->d_name, oq.count ? &oq : 0);
710 if (!(flags & REPO_NO_INTERNALIZE))
711 repodata_internalize(data);